From 0502a04d70bde4d090ac913e7e45869225a8d680 Mon Sep 17 00:00:00 2001 From: Jabar Asadi Date: Fri, 13 Oct 2023 16:58:51 +0200 Subject: [PATCH] feat: add reloader scenario tests --- apptests/appscenarios/appscenarios.go | 92 ++++++++++++++++++++++ apptests/appscenarios/appscenarios_test.go | 17 ++++ apptests/appscenarios/reloader.go | 84 ++++++++++++++++++++ apptests/appscenarios/reloader_test.go | 22 ++++++ apptests/environment/environment.go | 23 +++++- apptests/environment/environment_test.go | 6 ++ 6 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 apptests/appscenarios/appscenarios.go create mode 100644 apptests/appscenarios/appscenarios_test.go create mode 100644 apptests/appscenarios/reloader.go create mode 100644 apptests/appscenarios/reloader_test.go diff --git a/apptests/appscenarios/appscenarios.go b/apptests/appscenarios/appscenarios.go new file mode 100644 index 000000000..e80189b1c --- /dev/null +++ b/apptests/appscenarios/appscenarios.go @@ -0,0 +1,92 @@ +// Package appscenarios provides a set of application test scenarios that can be executed +// in a Kubernetes environment. The package defines an AppScenario interface that specifies the +// behavior and name of each scenario, and a List type that implements methods to execute, get, +// and check scenarios. +// +// The package currently supports one scenario for the reloader application, but more scenarios can be +// added by implementing the AppScenario interface and registering them in the scenariosList variable. +package appscenarios + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/mesosphere/kommander-applications/apptests/environment" +) + +// AppScenario defines the behavior and name of an application test scenario +type AppScenario interface { + Execute(context.Context, *environment.Env) error // logic implemented by a scenario + Name() string // scenario name +} + +type List map[string]AppScenario + +// Execute runs all the scenarios in the list and returns the first error encountered, if any. +func (s List) Execute(ctx context.Context, env *environment.Env) error { + for _, sc := range s { + if err := sc.Execute(ctx, env); err != nil { + return err + } + } + return nil +} + +// Get returns the associated scenario for the given application name, or nil if it does not exist. +func Get(application string) AppScenario { + s, ok := scenariosList[application] + if !ok { + return nil + } + return s +} + +// Has checks if the associated scenario for the given application exist. +func Has(application string) bool { + _, ok := scenariosList[application] + return ok +} + +// absolutePathTo returns the absolute path to the given application directory. +func absolutePathTo(application string) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + // determining the execution path. + var base string + _, err = os.Stat(filepath.Join(wd, "services")) + if os.IsNotExist(err) { + base = "../.." + } else { + base = "" + } + + dir, err := filepath.Abs(filepath.Join(wd, base, "services", application)) + if err != nil { + return "", err + } + + // filepath.Glob returns a sorted slice of matching paths + matches, err := filepath.Glob(filepath.Join(dir, "*")) + if err != nil { + return "", err + } + + if len(matches) == 0 { + return "", fmt.Errorf( + "no application directory found for %s in the given path:%s", + application, dir) + } + + return matches[0], nil + +} + +// This is the ScenarioList of all available scenarios. +var scenariosList = List{ + "reloader": reloader{}, +} diff --git a/apptests/appscenarios/appscenarios_test.go b/apptests/appscenarios/appscenarios_test.go new file mode 100644 index 000000000..ba166c36a --- /dev/null +++ b/apptests/appscenarios/appscenarios_test.go @@ -0,0 +1,17 @@ +package appscenarios + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAbsolutePathTo(t *testing.T) { + absAppPath, err := absolutePathTo("reloader") + assert.NoError(t, err) + + expected := filepath.Join("kommander-applications", "services", "reloader") + assert.Contains(t, absAppPath, expected) + assert.NotEmpty(t, filepath.Base(absAppPath)) +} diff --git a/apptests/appscenarios/reloader.go b/apptests/appscenarios/reloader.go new file mode 100644 index 000000000..71e3a92a6 --- /dev/null +++ b/apptests/appscenarios/reloader.go @@ -0,0 +1,84 @@ +package appscenarios + +import ( + "context" + "path/filepath" + "time" + + fluxhelmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" + "github.com/mesosphere/kommander-applications/apptests/environment" + "github.com/mesosphere/kommander-applications/apptests/flux" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + genericCLient "sigs.k8s.io/controller-runtime/pkg/client" +) + +type reloader struct{} + +func (r reloader) Name() string { + return "reloader" +} + +var _ AppScenario = (*reloader)(nil) + +const ( + pollInterval = 2 * time.Second + kommanderNamespace = "kommander" + kommanderFluxNamespace = "kommander-flux" +) + +func (r reloader) Execute(ctx context.Context, env *environment.Env) error { + appPath, err := absolutePathTo(r.Name()) + if err != nil { + return err + } + + // apply defaults config maps first + defaultKustomizations := filepath.Join(appPath, "/defaults") + err = env.ApplyKustomizations(ctx, defaultKustomizations, map[string]string{ + "releaseNamespace": kommanderNamespace, + }) + if err != nil { + return err + } + // apply the rest of kustomizations + err = env.ApplyKustomizations(ctx, appPath, map[string]string{ + "releaseNamespace": kommanderNamespace, + }) + if err != nil { + return err + } + + client, err := genericCLient.New(env.K8sClient.Config(), genericCLient.Options{Scheme: flux.NewScheme()}) + if err != nil { + return err + } + + hr := &fluxhelmv2beta1.HelmRelease{ + TypeMeta: metav1.TypeMeta{ + Kind: fluxhelmv2beta1.HelmReleaseKind, + APIVersion: fluxhelmv2beta1.GroupVersion.Version, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name(), + Namespace: kommanderNamespace, + }, + } + + err = wait.PollUntilContextCancel(ctx, pollInterval, true, func(ctx context.Context) (done bool, err error) { + err = client.Get(ctx, genericCLient.ObjectKeyFromObject(hr), hr) + if err != nil { + return false, err + } + + for _, cond := range hr.Status.Conditions { + if cond.Status == metav1.ConditionTrue && + cond.Type == fluxhelmv2beta1.ReleasedCondition { + return true, nil + } + } + return false, nil + }) + + return err +} diff --git a/apptests/appscenarios/reloader_test.go b/apptests/appscenarios/reloader_test.go new file mode 100644 index 000000000..0f2283f9c --- /dev/null +++ b/apptests/appscenarios/reloader_test.go @@ -0,0 +1,22 @@ +package appscenarios + +import ( + "context" + "testing" + + "github.com/mesosphere/kommander-applications/apptests/environment" + "github.com/stretchr/testify/assert" +) + +func TestListExecute(t *testing.T) { + env := &environment.Env{} + ctx := context.Background() + + err := env.Provision(ctx) + assert.NoError(t, err) + defer env.Destroy(ctx) + + r := reloader{} + err = r.Execute(ctx, env) + assert.NoError(t, err) +} diff --git a/apptests/environment/environment.go b/apptests/environment/environment.go index d5eb55ef3..4a6ed09cd 100644 --- a/apptests/environment/environment.go +++ b/apptests/environment/environment.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io" + "os" "path/filepath" "time" @@ -49,7 +50,7 @@ type Env struct { func (e *Env) Provision(ctx context.Context) error { var err error - kustomizePath, err := AbsolutePathToBase() + kustomizePath, err := absolutePathToBase() if err != nil { return err } @@ -217,7 +218,21 @@ func (e *Env) ApplyKustomizations(ctx context.Context, path string, substitution return nil } -// AbsolutePathToBase returns the absolute path to common/base directory. -func AbsolutePathToBase() (string, error) { - return filepath.Abs("../../common/base") +// absolutePathToBase returns the absolute path to common/base directory from the given working directory. +func absolutePathToBase() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + // determining the execution path. + var base string + _, err = os.Stat(filepath.Join(wd, "common", "base")) + if os.IsNotExist(err) { + base = "../.." + } else { + base = "" + } + + return filepath.Join(wd, base, "common", "base"), nil } diff --git a/apptests/environment/environment_test.go b/apptests/environment/environment_test.go index 9c09347fa..6ff092ea7 100644 --- a/apptests/environment/environment_test.go +++ b/apptests/environment/environment_test.go @@ -43,3 +43,9 @@ func TestProvision(t *testing.T) { } } + +func TestAbsolutePathToBase(t *testing.T) { + s, err := absolutePathToBase() + assert.NoError(t, err) + assert.Contains(t, s, "kommander-applications/common/base") +}