From ab09e8ecadd0976995e352db3e454c3ac2ce8657 Mon Sep 17 00:00:00 2001 From: Hasan Turken Date: Thu, 13 Oct 2022 13:22:55 +0300 Subject: [PATCH] Separate hooks for pre/post and setup/teardown Signed-off-by: Hasan Turken --- cmd/main.go | 34 +++++++++------ internal/config/config.go | 33 ++++++++------ internal/prepare.go | 19 ++++---- internal/templates/00-apply.yaml.tmpl | 8 +++- internal/templates/00-assert.yaml.tmpl | 8 ++-- internal/templates/01-assert.yaml.tmpl | 3 ++ internal/templates/renderer_test.go | 60 +++++++++++++------------- internal/tester.go | 44 +++++++++++-------- 8 files changed, 123 insertions(+), 86 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index fdb180c..6c7fc34 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -27,9 +27,10 @@ func main() { "'provider-aws/examples/s3/bucket.yaml,provider-gcp/examples/storage/bucket.yaml': "+ "The comma separated resources are used as test inputs.\n"+ "If this option is not set, 'MANIFEST_LIST' env var is used as default.").Envar("MANIFEST_LIST").String() - dataSourcePath = e2e.Flag("data-source", "File path of data source that will be used for injection some values.").Default("").String() - defaultHooksDirectory = e2e.Flag("default-hooks-directory", "Path to hooks directory for default hooks to run for all examples.\n"+ - "This could be overridden per resource using \"uptest.upbound.io/hooks-directory\" annotation.").String() + dataSourcePath = e2e.Flag("data-source", "File path of data source that will be used for injection some values.").Default("").String() + setupScript = e2e.Flag("setup-script", "Script that will be executed before running tests.").Default("").String() + teardownScript = e2e.Flag("teardown-script", "Script that will be executed after running tests.").Default("").String() + defaultTimeout = e2e.Flag("default-timeout", "Default timeout in seconds for the test.\n"+ "Timeout could be overridden per resource using \"uptest.upbound.io/timeout\" annotation.").Default("1200").Int() defaultConditions = e2e.Flag("default-conditions", "Comma seperated list of default conditions to wait for a successful test.\n"+ @@ -49,19 +50,28 @@ func main() { return } - defaultHooksPath := "" - if *defaultHooksDirectory != "" { - defaultHooksPath, err = filepath.Abs(*defaultHooksDirectory) + setupPath := "" + if *setupScript != "" { + setupPath, err = filepath.Abs(*setupScript) + if err != nil { + kingpin.FatalIfError(err, "cannot get absolute path of setup script") + } + } + + teardownPath := "" + if *teardownScript != "" { + teardownPath, err = filepath.Abs(*teardownScript) if err != nil { - kingpin.FatalIfError(err, "cannot get absolute path of default hooks directory") + kingpin.FatalIfError(err, "cannot get absolute path of teardown script") } } o := &config.AutomatedTest{ - ManifestPaths: examplePaths, - DataSourcePath: *dataSourcePath, - DefaultHooksDirPath: defaultHooksPath, - DefaultConditions: strings.Split(*defaultConditions, ","), - DefaultTimeout: *defaultTimeout, + ManifestPaths: examplePaths, + DataSourcePath: *dataSourcePath, + SetupScriptPath: setupPath, + TeardownScriptPath: teardownPath, + DefaultConditions: strings.Split(*defaultConditions, ","), + DefaultTimeout: *defaultTimeout, } kingpin.FatalIfError(internal.RunTest(o), "cannot run e2e tests successfully") diff --git a/internal/config/config.go b/internal/config/config.go index d9c3b73..b552fff 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,16 +2,26 @@ package config const ( AnnotationKeyTimeout = "uptest.upbound.io/timeout" - AnnotationKeyHooksDirectory = "uptest.upbound.io/hooks-directory" AnnotationKeyConditions = "uptest.upbound.io/conditions" + AnnotationKeyPreAssertHook = "uptest.upbound.io/pre-assert-hook" + AnnotationKeyPostAssertHook = "uptest.upbound.io/post-assert-hook" ) type AutomatedTest struct { - ManifestPaths []string - DataSourcePath string - DefaultTimeout int - DefaultHooksDirPath string - DefaultConditions []string + ManifestPaths []string + DataSourcePath string + + SetupScriptPath string + TeardownScriptPath string + + DefaultTimeout int + DefaultConditions []string +} + +type TestCase struct { + Timeout int + SetupScriptPath string + TeardownScriptPath string } type Resource struct { @@ -20,11 +30,8 @@ type Resource struct { KindGroup string Manifest string - Timeout int - HooksDirPath string - Conditions []string -} - -type TestCase struct { - Timeout int + Timeout int + Conditions []string + PreAssertScriptPath string + PostAssertScriptPath string } diff --git a/internal/prepare.go b/internal/prepare.go index eff857d..1dc65ac 100644 --- a/internal/prepare.go +++ b/internal/prepare.go @@ -53,18 +53,19 @@ type Preparer struct { dataSourcePath string } -func (p *Preparer) PrepareManifests() ([]*unstructured.Unstructured, error) { +func (p *Preparer) PrepareManifests() (map[string]*unstructured.Unstructured, error) { if err := os.MkdirAll(caseDirectory, os.ModePerm); err != nil { return nil, errors.Wrapf(err, "cannot create directory %s", caseDirectory) } - manifestData, err := p.injectVariables() + injectedFiles, err := p.injectVariables() if err != nil { return nil, errors.Wrap(err, "cannot inject variables") } - var manifests []*unstructured.Unstructured - for _, file := range manifestData { - decoder := kyaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(file), 1024) + + manifests := make(map[string]*unstructured.Unstructured, len(injectedFiles)) + for path, data := range injectedFiles { + decoder := kyaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(data), 1024) for { u := &unstructured.Unstructured{} if err := decoder.Decode(&u); err != nil { @@ -78,14 +79,14 @@ func (p *Preparer) PrepareManifests() ([]*unstructured.Unstructured, error) { fmt.Printf("Skipping %s with name %s since it requires the following manual intervention: %s\n", u.GroupVersionKind().String(), u.GetName(), v) continue } - manifests = append(manifests, u) + manifests[path] = u } } } return manifests, nil } -func (p *Preparer) injectVariables() ([]string, error) { +func (p *Preparer) injectVariables() (map[string]string, error) { dataSourceMap := make(map[string]string) if p.dataSourcePath != "" { dataSource, err := ioutil.ReadFile(p.dataSourcePath) @@ -97,7 +98,7 @@ func (p *Preparer) injectVariables() ([]string, error) { } } - var inputs []string + inputs := make(map[string]string, len(p.testFilePaths)) for _, f := range p.testFilePaths { manifestData, err := ioutil.ReadFile(f) if err != nil { @@ -107,7 +108,7 @@ func (p *Preparer) injectVariables() ([]string, error) { if err != nil { return nil, errors.Wrap(err, "cannot inject data source values") } - inputs = append(inputs, inputData) + inputs[f] = inputData } return inputs, nil } diff --git a/internal/templates/00-apply.yaml.tmpl b/internal/templates/00-apply.yaml.tmpl index 42756f1..f0ee6c6 100644 --- a/internal/templates/00-apply.yaml.tmpl +++ b/internal/templates/00-apply.yaml.tmpl @@ -1,4 +1,10 @@ -{{ range $resource := .Resources -}} +{{ if .TestCase.SetupScriptPath -}} +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- command: {{ .TestCase.SetupScriptPath }} +{{ end }} +{{- range $resource := .Resources -}} --- {{ $resource.Manifest }} {{- end }} \ No newline at end of file diff --git a/internal/templates/00-assert.yaml.tmpl b/internal/templates/00-assert.yaml.tmpl index 3e437ce..a8d55b1 100644 --- a/internal/templates/00-assert.yaml.tmpl +++ b/internal/templates/00-assert.yaml.tmpl @@ -4,8 +4,8 @@ timeout: {{ .TestCase.Timeout }} commands: - command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite {{- range $resource := .Resources }} -{{- if $resource.HooksDirPath }} -- script: if [ -f {{ $resource.HooksDirPath }}/pre.sh ]; then {{ $resource.HooksDirPath }}/pre.sh; else echo "No pre hook provided..."; fi +{{- if $resource.PreAssertScriptPath }} +- command: {{ $resource.PreAssertScriptPath }} {{- end }} {{- range $condition := $resource.Conditions }} {{- if $resource.Namespace }} @@ -14,7 +14,7 @@ commands: - command: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=condition={{ $condition }} --timeout 10s {{- end }} {{- end }} -{{- if $resource.HooksDirPath }} -- script: if [ -f {{ $resource.HooksDirPath }}/post.sh ]; then {{ $resource.HooksDirPath }}/post.sh; else echo "No post hook provided..."; fi +{{- if $resource.PostAssertScriptPath }} +- command: {{ $resource.PostAssertScriptPath }} {{- end }} {{- end }} diff --git a/internal/templates/01-assert.yaml.tmpl b/internal/templates/01-assert.yaml.tmpl index 2cda1c1..aa81cdb 100644 --- a/internal/templates/01-assert.yaml.tmpl +++ b/internal/templates/01-assert.yaml.tmpl @@ -10,3 +10,6 @@ commands: {{- end }} {{- end }} - command: ${KUBECTL} wait managed --all --for=delete --timeout 10s +{{- if .TestCase.TeardownScriptPath }} +- command: {{ .TestCase.TeardownScriptPath }} +{{- end }} diff --git a/internal/templates/renderer_test.go b/internal/templates/renderer_test.go index b9be256..612100e 100644 --- a/internal/templates/renderer_test.go +++ b/internal/templates/renderer_test.go @@ -35,8 +35,8 @@ func TestRender(t *testing.T) { resources []config.Resource } type want struct { - want map[string]string - err error + out map[string]string + err error } tests := map[string]struct { args args @@ -49,25 +49,22 @@ func TestRender(t *testing.T) { }, resources: []config.Resource{ { - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - Manifest: bucketManifest, - HooksDirPath: "test/bucket-hooks", - Conditions: []string{"Test"}, + Name: "example-bucket", + KindGroup: "s3.aws.upbound.io", + Manifest: bucketManifest, + Conditions: []string{"Test"}, }, }, }, want: want{ - want: map[string]string{ + out: map[string]string{ "00-apply.yaml": "---\n" + bucketManifest, "00-assert.yaml": `apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 10 commands: - command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: if [ -f test/bucket-hooks/pre.sh ]; then test/bucket-hooks/pre.sh; else echo "No pre hook provided..."; fi - command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: if [ -f test/bucket-hooks/post.sh ]; then test/bucket-hooks/post.sh; else echo "No post hook provided..."; fi `, "01-delete.yaml": `apiVersion: kuttl.dev/v1beta1 kind: TestStep @@ -87,41 +84,45 @@ commands: "SuccessMultipleResource": { args: args{ tc: &config.TestCase{ - Timeout: 10, + Timeout: 10, + SetupScriptPath: "/tmp/setup.sh", + TeardownScriptPath: "/tmp/teardown.sh", }, resources: []config.Resource{ { - Manifest: bucketManifest, - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - HooksDirPath: "test/bucket-hooks", - Conditions: []string{"Test"}, + Manifest: bucketManifest, + Name: "example-bucket", + KindGroup: "s3.aws.upbound.io", + PreAssertScriptPath: "/tmp/bucket/pre-assert.sh", + Conditions: []string{"Test"}, }, { - Name: "test-cluster-claim", - KindGroup: "cluster.gcp.platformref.upbound.io", - Namespace: "upbound-system", - Manifest: claimManifest, - HooksDirPath: "test/claim-hooks", - Conditions: []string{"Ready", "Synced"}, + Manifest: claimManifest, + Name: "test-cluster-claim", + KindGroup: "cluster.gcp.platformref.upbound.io", + Namespace: "upbound-system", + PostAssertScriptPath: "/tmp/claim/post-assert.sh", + Conditions: []string{"Ready", "Synced"}, }, }, }, want: want{ - want: map[string]string{ - "00-apply.yaml": "---\n" + bucketManifest + "---\n" + claimManifest, + out: map[string]string{ + "00-apply.yaml": `apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- command: /tmp/setup.sh +` + "---\n" + bucketManifest + "---\n" + claimManifest, "00-assert.yaml": `apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 10 commands: - command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: if [ -f test/bucket-hooks/pre.sh ]; then test/bucket-hooks/pre.sh; else echo "No pre hook provided..."; fi +- command: /tmp/bucket/pre-assert.sh - command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: if [ -f test/bucket-hooks/post.sh ]; then test/bucket-hooks/post.sh; else echo "No post hook provided..."; fi -- script: if [ -f test/claim-hooks/pre.sh ]; then test/claim-hooks/pre.sh; else echo "No pre hook provided..."; fi - command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Ready --timeout 10s --namespace upbound-system - command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Synced --timeout 10s --namespace upbound-system -- script: if [ -f test/claim-hooks/post.sh ]; then test/claim-hooks/post.sh; else echo "No post hook provided..."; fi +- command: /tmp/claim/post-assert.sh `, "01-delete.yaml": `apiVersion: kuttl.dev/v1beta1 kind: TestStep @@ -136,6 +137,7 @@ commands: - command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=delete --timeout 10s - command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=delete --timeout 10s --namespace upbound-system - command: ${KUBECTL} wait managed --all --for=delete --timeout 10s +- command: /tmp/teardown.sh `, }, }, @@ -147,7 +149,7 @@ commands: if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { t.Errorf("Render(...): -want error, +got error:\n%s", diff) } - if diff := cmp.Diff(tc.want.want, got); diff != "" { + if diff := cmp.Diff(tc.want.out, got); diff != "" { t.Errorf("Render(...): -want, +got:\n%s", diff) } }) diff --git a/internal/tester.go b/internal/tester.go index 9a77a51..ae517b1 100644 --- a/internal/tester.go +++ b/internal/tester.go @@ -18,7 +18,7 @@ import ( "github.com/upbound/uptest/internal/config" ) -func NewTester(manifests []*unstructured.Unstructured, opts *config.AutomatedTest) *Tester { +func NewTester(manifests map[string]*unstructured.Unstructured, opts *config.AutomatedTest) *Tester { return &Tester{ options: opts, manifests: manifests, @@ -27,7 +27,7 @@ func NewTester(manifests []*unstructured.Unstructured, opts *config.AutomatedTes type Tester struct { options *config.AutomatedTest - manifests []*unstructured.Unstructured + manifests map[string]*unstructured.Unstructured } func (t *Tester) ExecuteTests() error { @@ -50,11 +50,13 @@ func (t *Tester) ExecuteTests() error { func (t *Tester) prepareConfig() (*config.TestCase, []config.Resource, error) { tc := &config.TestCase{ - Timeout: t.options.DefaultTimeout, + Timeout: t.options.DefaultTimeout, + SetupScriptPath: t.options.SetupScriptPath, + TeardownScriptPath: t.options.TeardownScriptPath, } - examples := make([]config.Resource, len(t.manifests)) + examples := make([]config.Resource, 0, len(t.manifests)) - for i, m := range t.manifests { + for fp, m := range t.manifests { if m.GroupVersionKind().String() == "/v1, Kind=Secret" { continue } @@ -66,13 +68,12 @@ func (t *Tester) prepareConfig() (*config.TestCase, []config.Resource, error) { } example := config.Resource{ - Name: m.GetName(), - Namespace: m.GetNamespace(), - KindGroup: kg, - Manifest: string(d), - Timeout: t.options.DefaultTimeout, - HooksDirPath: t.options.DefaultHooksDirPath, - Conditions: t.options.DefaultConditions, + Name: m.GetName(), + Namespace: m.GetNamespace(), + KindGroup: kg, + Manifest: string(d), + Timeout: t.options.DefaultTimeout, + Conditions: t.options.DefaultConditions, } if v, ok := m.GetAnnotations()[config.AnnotationKeyTimeout]; ok { @@ -85,18 +86,25 @@ func (t *Tester) prepareConfig() (*config.TestCase, []config.Resource, error) { } } - if v, ok := m.GetAnnotations()[config.AnnotationKeyHooksDirectory]; ok { - example.HooksDirPath, err = filepath.Abs(v) + if v, ok := m.GetAnnotations()[config.AnnotationKeyConditions]; ok { + example.Conditions = strings.Split(v, ",") + } + + if v, ok := m.GetAnnotations()[config.AnnotationKeyPreAssertHook]; ok { + example.PreAssertScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(fp), filepath.Clean(v))) if err != nil { - return nil, nil, errors.Wrap(err, "cannot find absolute path for hooks directory") + return nil, nil, errors.Wrap(err, "cannot find absolute path for pre assert hook") } } - if v, ok := m.GetAnnotations()[config.AnnotationKeyConditions]; ok { - example.Conditions = strings.Split(v, ",") + if v, ok := m.GetAnnotations()[config.AnnotationKeyPostAssertHook]; ok { + example.PostAssertScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(fp), filepath.Clean(v))) + if err != nil { + return nil, nil, errors.Wrap(err, "cannot find absolute path for post assert hook") + } } - examples[i] = example + examples = append(examples, example) } return tc, examples, nil