Skip to content

Commit

Permalink
feat: added migrator for 0.8.8 (#950)
Browse files Browse the repository at this point in the history
* feat: added migrator for 0.8.8

* fix: fixed imports in process pkg

* fix: fixed imports in process pkg

* feat: ui in migrate command
  • Loading branch information
exu authored Feb 9, 2022
1 parent 3b38405 commit 2def158
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 0 deletions.
49 changes: 49 additions & 0 deletions cmd/kubectl-testkube/commands/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package commands

import (
"fmt"

"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"
"github.com/kubeshop/testkube/internal/migrations"
"github.com/kubeshop/testkube/pkg/ui"
"github.com/spf13/cobra"
)

func NewMigrateCmd() *cobra.Command {
var namespace string
cmd := &cobra.Command{
Use: "migrate",
Short: "migrate command",
Long: `migrate command manages migrations`,
Run: func(cmd *cobra.Command, args []string) {
ui.Logo()

client, _ := common.GetClient(cmd)
info, err := client.GetServerInfo()
ui.ExitOnError("getting server info", err)

if info.Version == "" {
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 {
ui.Warn("No migrations available for", info.Version)
}

for _, migration := range migrations {
fmt.Printf("- %+v - %s\n", migration.Version(), migration.Info())
}

err = migrator.Run(info.Version)
ui.ExitOnError("running migrations", err)
ui.Success("All migrations executed successfully")
},
}

cmd.Flags().StringVar(&namespace, "namespace", "testkube", "testkube namespace")

return cmd
}
1 change: 1 addition & 0 deletions cmd/kubectl-testkube/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func init() {
RootCmd.AddCommand(NewExecutorsCmd())
RootCmd.AddCommand(NewArtifactsCmd())
RootCmd.AddCommand(NewTestsCmd())
RootCmd.AddCommand(NewMigrateCmd())
}

var RootCmd = &cobra.Command{
Expand Down
9 changes: 9 additions & 0 deletions internal/migrations/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package migrations

import "github.com/kubeshop/testkube/pkg/migrator"

var Migrator migrator.Migrator

func init() {
Migrator = *migrator.NewMigrator()
}
33 changes: 33 additions & 0 deletions internal/migrations/version_0.8.8.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package migrations

// add migration to global migrator
func init() {
Migrator.Add(NewVersion_0_8_8())
}

func NewVersion_0_8_8() *Version_0_8_8 {
return &Version_0_8_8{}
}

type Version_0_8_8 struct {
}

func (m *Version_0_8_8) Version() string {
return "0.8.8"
}
func (m *Version_0_8_8) Migrate() error {
commands := []string{
`kubectl annotate --overwrite crds executors.executor.testkube.io meta.helm.sh/release-name="testkube" meta.helm.sh/release-namespace="testkube"`,
`kubectl annotate --overwrite crds tests.tests.testkube.io meta.helm.sh/release-name="testkube" meta.helm.sh/release-namespace="testkube"`,
`kubectl annotate --overwrite crds scripts.tests.testkube.io meta.helm.sh/release-name="testkube" meta.helm.sh/release-namespace="testkube"`,
`kubectl label --overwrite crds executors.executor.testkube.io app.kubernetes.io/managed-by=Helm`,
`kubectl label --overwrite crds tests.tests.testkube.io app.kubernetes.io/managed-by=Helm`,
`kubectl label --overwrite crds scripts.tests.testkube.io app.kubernetes.io/managed-by=Helm`,
}

_, err := Migrator.ExecuteCommands(commands)
return err
}
func (m *Version_0_8_8) Info() string {
return "Adding labels and annotations to Testkube CRDs"
}
81 changes: 81 additions & 0 deletions pkg/migrator/migrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package migrator

import (
"fmt"
"strings"

"github.com/kubeshop/testkube/pkg/log"
"github.com/kubeshop/testkube/pkg/process"
"github.com/kubeshop/testkube/pkg/version"
"go.uber.org/zap"
)

type Migration interface {
Migrate() error
Version() string
Info() string
}

func NewMigrator() *Migrator {
return &Migrator{
Log: log.DefaultLogger,
}
}

type Migrator struct {
Migrations []Migration
Log *zap.SugaredLogger
}

func (m *Migrator) Add(migration Migration) {
m.Migrations = append(m.Migrations, migration)
}

func (m *Migrator) GetValidMigrations(currentVersion string) (migrations []Migration) {
for _, migration := range m.Migrations {
if ok, err := m.IsValid(migration.Version(), currentVersion); ok && err == nil {
migrations = append(migrations, migration)
}
}

return
}

func (m *Migrator) Run(currentVersion string) error {
for _, migration := range m.GetValidMigrations(currentVersion) {
err := migration.Migrate()
if err != nil {
return err
}
}

return nil
}

// IsValid checks if versions constraints are met, assuming that currentVersion
// is just updated version and it should be taken for migration
func (m Migrator) IsValid(migrationVersion, currentVersion string) (bool, error) {

// clean possible v prefixes
migrationVersion = strings.TrimPrefix(migrationVersion, "v")
currentVersion = strings.TrimPrefix(currentVersion, "v")

if migrationVersion == "" || currentVersion == "" {
return false, fmt.Errorf("empty version migration:'%s', current:'%s'", migrationVersion, currentVersion)
}

return version.Lte(currentVersion, migrationVersion)
}

func (m Migrator) ExecuteCommands(commands []string) (outputs []string, err error) {
for _, command := range commands {
out, err := process.ExecuteString(command)
if err != nil {
return outputs, err
}

outputs = append(outputs, string(out))
}

return outputs, nil
}
122 changes: 122 additions & 0 deletions pkg/migrator/migrator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package migrator

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

var ErrMigrationFailed = fmt.Errorf("migration failed")

func TestMigrator(t *testing.T) {

t.Run("migrate versions one after another", func(t *testing.T) {
// given
migrator := NewMigrator()
migrator.Add(&Migr1{})
migrator.Add(&Migr2{})
migrator.Add(&Migr3{})

// when
migrator.Run("0.0.2")

// then
assert.Equal(t, migrator.Migrations[0].(*Migr1).Run, false)
assert.Equal(t, migrator.Migrations[1].(*Migr2).Run, true)
assert.Equal(t, migrator.Migrations[2].(*Migr3).Run, true)
})

t.Run("migrate mixed versions", func(t *testing.T) {
// given
migrator := NewMigrator()
migrator.Add(&Migr3{})
migrator.Add(&Migr1{})
migrator.Add(&Migr2{})
migrator.Add(&Migr1{})

// when
migrator.Run("0.0.2")

// then
assert.Equal(t, migrator.Migrations[0].(*Migr3).Run, true)
assert.Equal(t, migrator.Migrations[1].(*Migr1).Run, false)
assert.Equal(t, migrator.Migrations[2].(*Migr2).Run, true)
assert.Equal(t, migrator.Migrations[3].(*Migr1).Run, false)
})

t.Run("failed migration returns error", func(t *testing.T) {
// given
migrator := NewMigrator()
migrator.Add(&Migr1{})
migrator.Add(&MigrFailed{})
migrator.Add(&Migr1{})

// when
err := migrator.Run("0.0.1")

// then
assert.Error(t, err, ErrMigrationFailed)
})

}

type Migr1 struct {
Run bool
}

func (m *Migr1) Version() string {
return "0.0.1"
}
func (m *Migr1) Migrate() error {
m.Run = true
return nil
}
func (m *Migr1) Info() string {
return "some migration description 1"
}

type Migr2 struct {
Run bool
}

func (m *Migr2) Version() string {
return "0.0.2"
}
func (m *Migr2) Migrate() error {
m.Run = true
return nil
}
func (m *Migr2) Info() string {
return "some migration description 2"
}

type Migr3 struct {
Run bool
}

func (m *Migr3) Version() string {
return "0.0.3"
}
func (m *Migr3) Migrate() error {
m.Run = true
return nil
}
func (m *Migr3) Info() string {
return "some migration description 3"
}

type MigrFailed struct {
Run bool
}

func (m *MigrFailed) Version() string {
return "0.0.1"
}
func (m *MigrFailed) Migrate() error {
m.Run = true
return ErrMigrationFailed
}
func (m *MigrFailed) Info() string {
return "some failed migration"
}
18 changes: 18 additions & 0 deletions pkg/process/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os/exec"
"strings"
)

// Execute runs system command and returns whole output also in case of error
Expand Down Expand Up @@ -72,3 +73,20 @@ func ExecuteAsyncInDir(dir string, command string, arguments ...string) (cmd *ex

return cmd, nil
}

func ExecuteString(command string) (out []byte, err error) {
parts := strings.Split(command, " ")
if len(parts) == 1 {
out, err = Execute(parts[0])
} else if len(parts) > 1 {
out, err = Execute(parts[0], parts[1:]...)
} else {
return out, fmt.Errorf("invalid command to run '%s'", command)
}

if err != nil {
return out, fmt.Errorf("error: %w, output: %s", err, out)
}

return out, nil
}
9 changes: 9 additions & 0 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ func Lt(version1, version2 string) (bool, error) {
return v1.LessThan(v2), nil
}

func Lte(version1, version2 string) (bool, error) {
ok, err := Lt(version1, version2)
if err != nil {
return false, err
}

return ok || version1 == version2, nil
}

func validateVersionPostion(kind string) error {
if kind == Major || kind == Minor || kind == Patch {
return nil
Expand Down

0 comments on commit 2def158

Please sign in to comment.