-
Notifications
You must be signed in to change notification settings - Fork 64
feat: add support for provider migration jobs #357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
19c3f1b
feat: provider migrations
cloud-j-luna 8c4d78a
Merge branch 'main' into feature/migrations
cloud-j-luna e979e6b
refactor: reduce duplicate code
cloud-j-luna 0e4c878
Merge branch 'main' into feature/migrations
cloud-j-luna 2d05dd1
Merge branch 'main' into feature/migrations
cloud-j-luna 38f4642
chore: set kubeconfig for dev on .envrc
cloud-j-luna 0e9c1bd
refactor: change migrations-enabled default to true
cloud-j-luna 70dea84
chore: add nolint to test write file
cloud-j-luna 55dc4e1
chore: remove example migration
cloud-j-luna File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,14 +2,14 @@ package cmd | |
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| "pkg.akt.dev/go/cli" | ||
| ) | ||
|
|
||
| func migrate() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "migrate", | ||
| PersistentPreRunE: cli.TxPersistentPreRunE, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed this as a migrations are not ran with transaction contexts. Also it seems |
||
| Use: "migrate", | ||
| } | ||
|
|
||
| cmd.AddCommand(MigrateRunCmd()) | ||
|
|
||
| return cmd | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "path/filepath" | ||
|
|
||
| "cosmossdk.io/log" | ||
| "github.com/spf13/cobra" | ||
| "k8s.io/client-go/kubernetes" | ||
| cflags "pkg.akt.dev/go/cli/flags" | ||
| "pkg.akt.dev/go/util/ctxlog" | ||
|
|
||
| "github.com/akash-network/provider/cluster/kube/clientcommon" | ||
| providerflags "github.com/akash-network/provider/cmd/provider-services/cmd/flags" | ||
| "github.com/akash-network/provider/cmd/provider-services/cmd/util" | ||
| "github.com/akash-network/provider/migrations" | ||
| "github.com/akash-network/provider/tools/fromctx" | ||
| "github.com/akash-network/provider/version" | ||
| ) | ||
|
|
||
| func MigrateRunCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "run", | ||
| Short: "run pending migrations", | ||
| SilenceUsage: true, | ||
| PreRunE: func(cmd *cobra.Command, args []string) error { | ||
| if err := clientcommon.SetKubeConfigToCmd(cmd); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| logger := util.OpenLogger() | ||
| ctx := ctxlog.WithLogger(cmd.Context(), logger) | ||
| cmd.SetContext(ctx) | ||
|
|
||
| kubecfg := fromctx.MustKubeConfigFromCtx(ctx) | ||
| kc, err := kubernetes.NewForConfig(kubecfg) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create kubernetes client: %w", err) | ||
| } | ||
|
|
||
| fromctx.CmdSetContextValue(cmd, fromctx.CtxKeyKubeClientSet, kc) | ||
|
|
||
| return nil | ||
| }, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return doMigrateRun(cmd.Context(), cmd) | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().String(FlagMigrationsStatePath, "", "path to migrations state file (default: $AP_HOME/migrations.json)") | ||
|
|
||
| if err := providerflags.AddKubeConfigPathFlag(cmd); err != nil { | ||
| panic(err.Error()) | ||
| } | ||
|
cloud-j-luna marked this conversation as resolved.
|
||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| func doMigrateRun(ctx context.Context, cmd *cobra.Command) error { | ||
| logger := ctxlog.LogcFromCtx(ctx) | ||
|
|
||
| statePath, err := determineStatePath(cmd) | ||
| if err != nil { | ||
| return fmt.Errorf("determining state path: %w", err) | ||
| } | ||
|
|
||
| result, err := runMigrations(ctx, statePath, logger) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Log completion with success/failed counts (even if there are errors) | ||
| logger.Info("migrations completed", "successful", result.SuccessCount, "failed", len(result.Errs)) | ||
|
|
||
| if len(result.Errs) > 0 { | ||
| return fmt.Errorf("%d migration(s) failed", len(result.Errs)) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func determineStatePath(cmd *cobra.Command) (string, error) { | ||
| statePath, err := cmd.Flags().GetString(FlagMigrationsStatePath) | ||
| if err != nil { | ||
| return "", fmt.Errorf("getting migrations state path flag: %w", err) | ||
| } | ||
|
|
||
| if statePath == "" { | ||
| homeDir, err := cmd.Flags().GetString(cflags.FlagHome) | ||
| if err != nil { | ||
| // Try persistent flags if not found in regular flags | ||
| homeDir, err = cmd.PersistentFlags().GetString(cflags.FlagHome) | ||
| if err != nil { | ||
| return "", fmt.Errorf("unable to get home directory flag: %w", err) | ||
| } | ||
| } | ||
| if homeDir == "" { | ||
| return "", fmt.Errorf("home directory flag is not set") | ||
| } | ||
| statePath = filepath.Join(homeDir, "migrations.json") | ||
| } | ||
|
|
||
| return statePath, nil | ||
| } | ||
|
|
||
| func setCurrentVersionOrDefault(registry *migrations.Registry) string { | ||
| currentVersion := version.Version | ||
| if currentVersion == "" { | ||
| currentVersion = "0.0.0" | ||
| } | ||
| registry.SetCurrentVersion(currentVersion) | ||
|
|
||
| return currentVersion | ||
| } | ||
|
|
||
| // runMigrationsResult contains the results of running migrations. | ||
| type runMigrationsResult struct { | ||
| SuccessCount int | ||
| Errs []error | ||
| } | ||
|
|
||
| // runMigrations executes pending migrations with the provided state path. | ||
| // The logContext parameter is used to customize log messages (e.g., "running pending migrations" vs "running pending migrations on startup"). | ||
| // The logMigrationStatus parameter controls whether detailed migration status is logged for each migration. | ||
| // Returns the migration results and an error if the migration process itself failed (not individual migration errors). | ||
| func runMigrations(ctx context.Context, statePath string, logger log.Logger) (*runMigrationsResult, error) { | ||
| stateManager := migrations.NewStateManager(statePath) | ||
| registry := migrations.NewRegistry(stateManager) | ||
|
|
||
| currentVersion := setCurrentVersionOrDefault(registry) | ||
|
|
||
| previousVersion, err := stateManager.GetProviderVersion() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get previous provider version: %w", err) | ||
| } | ||
|
|
||
| if previousVersion == "" { | ||
| logger.Info("fresh install detected, marking all migrations as applied", "current_version", currentVersion) | ||
| allMigrations := migrations.GetAll() | ||
| for _, m := range allMigrations { | ||
| if err := stateManager.MarkApplied(m.Name()); err != nil { | ||
| return nil, fmt.Errorf("failed to mark migration %q as applied: %w", m.Name(), err) | ||
| } | ||
| } | ||
| if err := stateManager.SetProviderVersion(currentVersion); err != nil { | ||
| return nil, fmt.Errorf("failed to set provider version: %w", err) | ||
| } | ||
| return &runMigrationsResult{SuccessCount: 0, Errs: nil}, nil | ||
| } | ||
|
|
||
| logger.Info("checking for migrations", "previous_version", previousVersion, "current_version", currentVersion) | ||
|
|
||
| allMigrations := migrations.GetAll() | ||
| logger.Info("discovered migrations", "count", len(allMigrations)) | ||
| if len(allMigrations) == 0 { | ||
| logger.Info("no migrations found") | ||
| if err := stateManager.SetProviderVersion(currentVersion); err != nil { | ||
| return nil, fmt.Errorf("failed to update provider version: %w", err) | ||
| } | ||
| return &runMigrationsResult{SuccessCount: 0, Errs: nil}, nil | ||
| } | ||
|
|
||
| for _, m := range allMigrations { | ||
| logger.Info("registered migration", "name", m.Name(), "description", m.Description(), "from_version", m.FromVersion()) | ||
|
cloud-j-luna marked this conversation as resolved.
|
||
| applied, _ := stateManager.IsApplied(m.Name()) | ||
|
cloud-j-luna marked this conversation as resolved.
|
||
| logger.Info("migration status", "name", m.Name(), "applied", applied, "previous_version", previousVersion, "from_version", m.FromVersion()) | ||
| } | ||
|
|
||
| pending, err := registry.GetPending(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get pending migrations: %w", err) | ||
| } | ||
|
|
||
| if len(pending) == 0 { | ||
| logger.Info("no pending migrations") | ||
| if err := stateManager.SetProviderVersion(currentVersion); err != nil { | ||
| return nil, fmt.Errorf("failed to update provider version: %w", err) | ||
| } | ||
| return &runMigrationsResult{SuccessCount: 0, Errs: nil}, nil | ||
| } | ||
|
|
||
| logger.Info("running pending migrations", "count", len(pending)) | ||
|
|
||
| successCount, errs := registry.RunMigrations(ctx) | ||
|
|
||
| if len(errs) > 0 { | ||
| for _, err := range errs { | ||
| logger.Error("migration error", "err", err) | ||
| } | ||
| return &runMigrationsResult{SuccessCount: successCount, Errs: errs}, nil | ||
|
cloud-j-luna marked this conversation as resolved.
|
||
| } | ||
|
|
||
| logger.Info("migrations completed successfully", "count", successCount) | ||
| return &runMigrationsResult{SuccessCount: successCount, Errs: nil}, nil | ||
| } | ||
|
cloud-j-luna marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package migrations | ||
|
|
||
| import ( | ||
| "context" | ||
| ) | ||
|
|
||
| // Migration represents a single migration that can be executed. | ||
| // All migrations must be idempotent, running them multiple times | ||
| // should have the same effect as running them once. | ||
| type Migration interface { | ||
| // Name returns a unique identifier for this migration. | ||
| // This identifier is used to track whether the migration has been applied. | ||
| Name() string | ||
|
|
||
| // Description returns a human-readable description of what this migration does. | ||
| Description() string | ||
|
|
||
| // FromVersion returns the provider version this migration applies from. | ||
| // Migrations will only run when upgrading from a version < FromVersion. | ||
| // For fresh installs (no previous version), migrations are skipped. | ||
| // Return empty string to indicate this migration should always run (not recommended). | ||
| FromVersion() string | ||
|
|
||
| // Run executes the migration. This method must be idempotent. | ||
| // If the migration has already been applied, this should be a no-op. | ||
| // The context provides access to Kubernetes clients and other resources. | ||
| // The registry tracks applied migrations via the state file, so Run() | ||
| // should be safe to call multiple times. | ||
| Run(ctx context.Context) error | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.