From dae724826548b10f3f2a3441b945cc7a41d1e9c6 Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Fri, 18 Feb 2022 13:39:19 +0300 Subject: [PATCH] feat: add server migration for tests and scripts (#972) * ref: rename test - test-suite in opanapi spec nad model part1 * rename _test to _test_suite * fix: generated testsuite from openapispec * chore: rename TestKube to Testkube * rename _script to _test * rename _script to _test part2 * testName to testSuiteName and scriptName to testName * api client refactor * chore: rename testsuite to test and scripts to tests - commands * script to test refactor * mappers renaming * scripts * more rename * more scripts renaming * feat: bump operator version * fix: using new operator clients and apis * docs update * fixed tests * renamed proxy client in tests * docs - generated from cobra commandfs * fix: integration tests * fix: more scripts to tests renaming * fix: more scripts to tests renaming * fix: docs with testsuites * fix: renaming * fix: renaming * fix: renaming in postman tests * feat: new testkube logos * feat: new squishies image * feat: add server migration for 0.9.2 * fix: tune migration for tests and scripts * fix: remove commented code * fix: rename isValid method * fix: update message text * fix: nil pointer panic * fix: kubectl plugin test suite fix * fix: remove merging conflict Co-authored-by: Jacek Wysocki Co-authored-by: Vladislav Sukhin --- cmd/api-server/main.go | 32 +++- cmd/kubectl-testkube/commands/common.go | 10 +- internal/migrations/version_0.8.8.go | 6 + internal/migrations/version_0.9.2.go | 185 ++++++++++++++++++++++++ pkg/migrator/migrator.go | 26 +++- pkg/migrator/migrator_test.go | 65 ++++++++- 6 files changed, 310 insertions(+), 14 deletions(-) create mode 100644 internal/migrations/version_0.9.2.go diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 21c461c9e08..3c00176bba0 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -2,18 +2,24 @@ package main import ( "flag" + "fmt" "net" "os" "github.com/kelseyhightower/envconfig" kubeclient "github.com/kubeshop/testkube-operator/client" executorsclientv1 "github.com/kubeshop/testkube-operator/client/executors" + scriptsclient "github.com/kubeshop/testkube-operator/client/scripts/v2" + testsclientv1 "github.com/kubeshop/testkube-operator/client/tests" testsclientv2 "github.com/kubeshop/testkube-operator/client/tests/v2" testsuitesclientv1 "github.com/kubeshop/testkube-operator/client/testsuites/v1" apiv1 "github.com/kubeshop/testkube/internal/app/api/v1" + "github.com/kubeshop/testkube/internal/migrations" + "github.com/kubeshop/testkube/internal/pkg/api" "github.com/kubeshop/testkube/internal/pkg/api/repository/result" "github.com/kubeshop/testkube/internal/pkg/api/repository/storage" "github.com/kubeshop/testkube/internal/pkg/api/repository/testresult" + "github.com/kubeshop/testkube/pkg/migrator" "github.com/kubeshop/testkube/pkg/secret" "github.com/kubeshop/testkube/pkg/telemetry" "github.com/kubeshop/testkube/pkg/ui" @@ -34,6 +40,21 @@ func init() { envconfig.Process("mongo", &Config) } +func runMigrations() (err error) { + ui.Info("Available migrations for", api.Version) + results := migrations.Migrator.GetValidMigrations(api.Version, migrator.MigrationTypeServer) + if len(results) == 0 { + ui.Warn("No migrations available for", api.Version) + return nil + } + + for _, migration := range results { + fmt.Printf("- %+v - %s\n", migration.Version(), migration.Info()) + } + + return migrations.Migrator.Run(api.Version, migrator.MigrationTypeServer) +} + func main() { telemetry.CollectAnonymousInfo() @@ -55,17 +76,24 @@ func main() { secretClient, err := secret.NewClient() ui.ExitOnError("Getting secret client", err) - testsClient := testsclientv2.NewClient(kubeClient) + scriptsClient := scriptsclient.NewClient(kubeClient) + testsClientV1 := testsclientv1.NewClient(kubeClient) + testsClientV2 := testsclientv2.NewClient(kubeClient) executorsClient := executorsclientv1.NewClient(kubeClient) testsuitesClient := testsuitesclientv1.NewClient(kubeClient) resultsRepository := result.NewMongoRespository(db) testResultsRepository := testresult.NewMongoRespository(db) + migrations.Migrator.Add(migrations.NewVersion_0_9_2(scriptsClient, testsClientV1, testsClientV2, testsuitesClient)) + if err := runMigrations(); err != nil { + ui.ExitOnError("Running server migrations", err) + } + err = apiv1.NewServer( resultsRepository, testResultsRepository, - testsClient, + testsClientV2, executorsClient, testsuitesClient, secretClient, diff --git a/cmd/kubectl-testkube/commands/common.go b/cmd/kubectl-testkube/commands/common.go index 80a8b2a5f61..255646f5c86 100644 --- a/cmd/kubectl-testkube/commands/common.go +++ b/cmd/kubectl-testkube/commands/common.go @@ -7,6 +7,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/internal/migrations" + "github.com/kubeshop/testkube/pkg/migrator" "github.com/kubeshop/testkube/pkg/process" "github.com/kubeshop/testkube/pkg/ui" "github.com/spf13/cobra" @@ -21,19 +22,18 @@ func RunMigrations(cmd *cobra.Command) (hasMigrations bool, err error) { ui.Failf("Can't detect cluster version") } - migrator := migrations.Migrator ui.Info("Available migrations for", info.Version) - migrations := migrator.GetValidMigrations(info.Version) - if len(migrations) == 0 { + results := migrations.Migrator.GetValidMigrations(info.Version, migrator.MigrationTypeClient) + if len(results) == 0 { ui.Warn("No migrations available for", info.Version) return false, nil } - for _, migration := range migrations { + for _, migration := range results { fmt.Printf("- %+v - %s\n", migration.Version(), migration.Info()) } - return true, migrator.Run(info.Version) + return true, migrations.Migrator.Run(info.Version, migrator.MigrationTypeClient) } func HelmUpgradeOrInstalTestkube(name, namespace, chart string, noDashboard, noMinio, noJetstack bool) error { diff --git a/internal/migrations/version_0.8.8.go b/internal/migrations/version_0.8.8.go index 08fc9714c30..a0e0ecb3b36 100644 --- a/internal/migrations/version_0.8.8.go +++ b/internal/migrations/version_0.8.8.go @@ -1,5 +1,7 @@ package migrations +import "github.com/kubeshop/testkube/pkg/migrator" + // add migration to global migrator func init() { Migrator.Add(NewVersion_0_8_8()) @@ -31,3 +33,7 @@ func (m *Version_0_8_8) Migrate() error { func (m *Version_0_8_8) Info() string { return "Adding labels and annotations to Testkube CRDs" } + +func (m *Version_0_8_8) Type() migrator.MigrationType { + return migrator.MigrationTypeClient +} diff --git a/internal/migrations/version_0.9.2.go b/internal/migrations/version_0.9.2.go new file mode 100644 index 00000000000..2a9d788a5c9 --- /dev/null +++ b/internal/migrations/version_0.9.2.go @@ -0,0 +1,185 @@ +package migrations + +import ( + "os" + "strings" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + testsv1 "github.com/kubeshop/testkube-operator/apis/tests/v1" + testsv2 "github.com/kubeshop/testkube-operator/apis/tests/v2" + testsuite "github.com/kubeshop/testkube-operator/apis/testsuite/v1" + scriptsclientv2 "github.com/kubeshop/testkube-operator/client/scripts/v2" + testsclientv1 "github.com/kubeshop/testkube-operator/client/tests" + testsclientv2 "github.com/kubeshop/testkube-operator/client/tests/v2" + testsuitesclientv1 "github.com/kubeshop/testkube-operator/client/testsuites/v1" + "github.com/kubeshop/testkube/pkg/migrator" +) + +func NewVersion_0_9_2( + scriptsClient *scriptsclientv2.ScriptsClient, + testsClientV1 *testsclientv1.TestsClient, + testsClientV2 *testsclientv2.TestsClient, + testsuitesClient *testsuitesclientv1.TestSuitesClient, +) *Version_0_9_2 { + return &Version_0_9_2{ + scriptsClient: scriptsClient, + testsClientV1: testsClientV1, + testsClientV2: testsClientV2, + testsuitesClient: testsuitesClient, + } +} + +type Version_0_9_2 struct { + scriptsClient *scriptsclientv2.ScriptsClient + testsClientV1 *testsclientv1.TestsClient + testsClientV2 *testsclientv2.TestsClient + testsuitesClient *testsuitesclientv1.TestSuitesClient + namespace string +} + +func (m *Version_0_9_2) Version() string { + return "0.9.2" +} +func (m *Version_0_9_2) Migrate() error { + namespace := os.Getenv("TESTKUBE_NAMESPACE") + + scripts, err := m.scriptsClient.List(namespace, nil) + if err != nil { + return err + } + + for _, script := range scripts.Items { + if _, err = m.testsClientV2.Get(namespace, script.Name); err != nil && !errors.IsNotFound(err) { + return err + } + + if err == nil { + continue + } + + test := &testsv2.Test{ + ObjectMeta: metav1.ObjectMeta{ + Name: script.Name, + Namespace: script.Namespace, + }, + Spec: testsv2.TestSpec{ + Type_: script.Spec.Type_, + Name: script.Spec.Name, + Params: script.Spec.Params, + Tags: script.Spec.Tags, + }, + } + + if script.Spec.Content != nil { + test.Spec.Content = &testsv2.TestContent{ + Type_: script.Spec.Content.Type_, + Data: script.Spec.Content.Data, + Uri: script.Spec.Content.Uri, + } + + if script.Spec.Content.Repository != nil { + test.Spec.Content.Repository = &testsv2.Repository{ + Type_: script.Spec.Content.Repository.Type_, + Uri: script.Spec.Content.Repository.Uri, + Branch: script.Spec.Content.Repository.Branch, + Path: script.Spec.Content.Repository.Path, + } + } + } + + if _, err = m.testsClientV2.Create(test); err != nil { + return err + } + + if err = m.scriptsClient.Delete(namespace, script.Name); err != nil { + return err + } + } + + tests, err := m.testsClientV1.List(namespace, nil) + if err != nil { + return err + } + +OUTER: + for _, test := range tests.Items { + if _, err = m.testsuitesClient.Get(namespace, test.Name); err != nil && !errors.IsNotFound(err) { + return err + } + + if err == nil { + continue + } + + for _, managedField := range test.GetManagedFields() { + if !strings.HasSuffix(managedField.APIVersion, "/v1") { + continue OUTER + } + } + + testsuite := &testsuite.TestSuite{ + ObjectMeta: metav1.ObjectMeta{ + Name: test.Name, + Namespace: test.Namespace, + }, + Spec: testsuite.TestSuiteSpec{ + Repeats: test.Spec.Repeats, + Description: test.Spec.Description, + Tags: test.Spec.Tags, + }, + } + + for _, step := range test.Spec.Before { + testsuite.Spec.Before = append(testsuite.Spec.Before, copyTestStepTest2Testsuite(step)) + } + + for _, step := range test.Spec.Steps { + testsuite.Spec.Steps = append(testsuite.Spec.Steps, copyTestStepTest2Testsuite(step)) + } + + for _, step := range test.Spec.After { + testsuite.Spec.After = append(testsuite.Spec.After, copyTestStepTest2Testsuite(step)) + } + + if _, err = m.testsuitesClient.Create(testsuite); err != nil { + return err + } + + if err = m.testsClientV1.Delete(namespace, test.Name); err != nil { + return err + } + } + + return nil +} +func (m *Version_0_9_2) Info() string { + return "Moving scripts v2 resources to tests v2 ones and tests v1 resources to testsuites v1 ones" +} + +func (m *Version_0_9_2) Type() migrator.MigrationType { + return migrator.MigrationTypeServer +} + +func copyTestStepTest2Testsuite(step testsv1.TestStepSpec) testsuite.TestSuiteStepSpec { + result := testsuite.TestSuiteStepSpec{ + Type: step.Type, + } + + if step.Execute != nil { + result.Execute = &testsuite.TestSuiteStepExecute{ + Namespace: step.Execute.Namespace, + Name: step.Execute.Name, + StopOnFailure: step.Execute.StopOnFailure, + } + } + + if step.Delay != nil { + result.Delay = &testsuite.TestSuiteStepDelay{ + Duration: step.Delay.Duration, + } + } + + return result +} diff --git a/pkg/migrator/migrator.go b/pkg/migrator/migrator.go index 76a94bd9949..723cd92eb0a 100644 --- a/pkg/migrator/migrator.go +++ b/pkg/migrator/migrator.go @@ -14,8 +14,19 @@ type Migration interface { Migrate() error Version() string Info() string + Type() MigrationType } +// MigrationType is migration type +type MigrationType int + +const ( + // MigrationTypeClient is client migration type + MigrationTypeClient MigrationType = iota + 1 + // MigrationTypeServer is server migration type + MigrationTypeServer +) + func NewMigrator() *Migrator { return &Migrator{ Log: log.DefaultLogger, @@ -31,18 +42,25 @@ func (m *Migrator) Add(migration Migration) { m.Migrations = append(m.Migrations, migration) } -func (m *Migrator) GetValidMigrations(currentVersion string) (migrations []Migration) { +func (m *Migrator) GetValidMigrations(currentVersion string, migrationTypes ...MigrationType) (migrations []Migration) { + types := make(map[MigrationType]struct{}, len(migrationTypes)) + for _, migrationType := range migrationTypes { + types[migrationType] = struct{}{} + } + for _, migration := range m.Migrations { if ok, err := m.IsValid(migration.Version(), currentVersion); ok && err == nil { - migrations = append(migrations, migration) + if _, ok = types[migration.Type()]; ok { + migrations = append(migrations, migration) + } } } return } -func (m *Migrator) Run(currentVersion string) error { - for _, migration := range m.GetValidMigrations(currentVersion) { +func (m *Migrator) Run(currentVersion string, migrationTypes ...MigrationType) error { + for _, migration := range m.GetValidMigrations(currentVersion, migrationTypes...) { err := migration.Migrate() if err != nil { return err diff --git a/pkg/migrator/migrator_test.go b/pkg/migrator/migrator_test.go index 4cd56790613..a737ae7fb9f 100644 --- a/pkg/migrator/migrator_test.go +++ b/pkg/migrator/migrator_test.go @@ -19,7 +19,7 @@ func TestMigrator(t *testing.T) { migrator.Add(&Migr3{}) // when - migrator.Run("0.0.2") + migrator.Run("0.0.2", MigrationTypeClient) // then assert.Equal(t, migrator.Migrations[0].(*Migr1).Run, false) @@ -36,7 +36,7 @@ func TestMigrator(t *testing.T) { migrator.Add(&Migr1{}) // when - migrator.Run("0.0.2") + migrator.Run("0.0.2", MigrationTypeClient) // then assert.Equal(t, migrator.Migrations[0].(*Migr3).Run, true) @@ -53,12 +53,40 @@ func TestMigrator(t *testing.T) { migrator.Add(&Migr1{}) // when - err := migrator.Run("0.0.1") + err := migrator.Run("0.0.1", MigrationTypeClient) // then assert.Error(t, err, ErrMigrationFailed) }) + t.Run("run only client migration", func(t *testing.T) { + // given + migrator := NewMigrator() + migrator.Add(&Migr1{}) + migrator.Add(&MigrServer{}) + + // when + migrator.Run("0.0.1", MigrationTypeClient) + + // then + assert.Equal(t, migrator.Migrations[0].(*Migr1).Run, true) + assert.Equal(t, migrator.Migrations[1].(*MigrServer).Run, false) + }) + + t.Run("run only server migration", func(t *testing.T) { + // given + migrator := NewMigrator() + migrator.Add(&Migr1{}) + migrator.Add(&MigrServer{}) + + // when + migrator.Run("0.0.1", MigrationTypeServer) + + // then + assert.Equal(t, migrator.Migrations[0].(*Migr1).Run, false) + assert.Equal(t, migrator.Migrations[1].(*MigrServer).Run, true) + }) + } type Migr1 struct { @@ -76,6 +104,10 @@ func (m *Migr1) Info() string { return "some migration description 1" } +func (m *Migr1) Type() MigrationType { + return MigrationTypeClient +} + type Migr2 struct { Run bool } @@ -90,6 +122,9 @@ func (m *Migr2) Migrate() error { func (m *Migr2) Info() string { return "some migration description 2" } +func (m *Migr2) Type() MigrationType { + return MigrationTypeClient +} type Migr3 struct { Run bool @@ -105,6 +140,9 @@ func (m *Migr3) Migrate() error { func (m *Migr3) Info() string { return "some migration description 3" } +func (m *Migr3) Type() MigrationType { + return MigrationTypeClient +} type MigrFailed struct { Run bool @@ -120,3 +158,24 @@ func (m *MigrFailed) Migrate() error { func (m *MigrFailed) Info() string { return "some failed migration" } +func (m *MigrFailed) Type() MigrationType { + return MigrationTypeClient +} + +type MigrServer struct { + Run bool +} + +func (m *MigrServer) Version() string { + return "0.0.1" +} +func (m *MigrServer) Migrate() error { + m.Run = true + return nil +} +func (m *MigrServer) Info() string { + return "some server migration" +} +func (m *MigrServer) Type() MigrationType { + return MigrationTypeServer +}