generated from kyma-project/template-repository
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #450 from akgalwas/migrator-refactor
Migrator refactor
- Loading branch information
Showing
13 changed files
with
816 additions
and
723 deletions.
There are no files selected for viewing
This file contains 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,133 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/gardener/gardener/pkg/apis/core/v1beta1" | ||
gardener_types "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1" | ||
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/config" | ||
"github.com/kyma-project/infrastructure-manager/pkg/gardener" | ||
"github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig" | ||
"github.com/pkg/errors" | ||
"log" | ||
"log/slog" | ||
"os" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
logf "sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/log/zap" | ||
"time" | ||
) | ||
|
||
const ( | ||
contextTimeout = 5 * time.Minute | ||
expirationTime = 60 * time.Minute | ||
runtimeIDAnnotation = "kcp.provisioner.kyma-project.io/runtime-id" | ||
) | ||
|
||
func main() { | ||
slog.Info("Starting runtime-migrator") | ||
cfg := config.NewConfig() | ||
|
||
opts := zap.Options{ | ||
Development: true, | ||
} | ||
logger := zap.New(zap.UseFlagOptions(&opts)) | ||
logf.SetLogger(logger) | ||
|
||
converterConfig, err := config.LoadConverterConfig(cfg) | ||
if err != nil { | ||
slog.Error(fmt.Sprintf("Unable to load converter config: %v", err)) | ||
os.Exit(1) | ||
} | ||
|
||
gardenerNamespace := fmt.Sprintf("garden-%s", cfg.GardenerProjectName) | ||
|
||
kubeconfigProvider, err := setupKubernetesKubeconfigProvider(cfg.GardenerKubeconfigPath, gardenerNamespace, expirationTime) | ||
if err != nil { | ||
slog.Error(fmt.Sprintf("Failed to create kubeconfig provider: %v", err)) | ||
os.Exit(1) | ||
} | ||
|
||
kcpClient, err := config.CreateKcpClient(&cfg) | ||
if err != nil { | ||
slog.Error("Failed to create kcp client: %v ", err) | ||
os.Exit(1) | ||
} | ||
|
||
gardenerShootClient, err := setupGardenerShootClient(cfg.GardenerKubeconfigPath, gardenerNamespace) | ||
if err != nil { | ||
slog.Error("Failed to setup Gardener shoot client: %v", err) | ||
os.Exit(1) | ||
} | ||
|
||
slog.Info("Migrating runtimes") | ||
migrator, err := NewMigration(cfg, converterConfig, kubeconfigProvider, kcpClient, gardenerShootClient) | ||
if err != nil { | ||
slog.Error("Failed to create migrator: %v", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = migrator.Do(getRuntimeIDsFromStdin(cfg)) | ||
if err != nil { | ||
slog.Error(fmt.Sprintf("Failed to migrate runtimes: %v", err)) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func setupKubernetesKubeconfigProvider(kubeconfigPath string, namespace string, expirationTime time.Duration) (kubeconfig.Provider, error) { | ||
restConfig, err := gardener.NewRestConfigFromFile(kubeconfigPath) | ||
if err != nil { | ||
return kubeconfig.Provider{}, err | ||
} | ||
|
||
gardenerClientSet, err := gardener_types.NewForConfig(restConfig) | ||
if err != nil { | ||
return kubeconfig.Provider{}, err | ||
} | ||
|
||
gardenerClient, err := client.New(restConfig, client.Options{}) | ||
if err != nil { | ||
return kubeconfig.Provider{}, err | ||
} | ||
|
||
shootClient := gardenerClientSet.Shoots(namespace) | ||
dynamicKubeconfigAPI := gardenerClient.SubResource("adminkubeconfig") | ||
|
||
err = v1beta1.AddToScheme(gardenerClient.Scheme()) | ||
if err != nil { | ||
return kubeconfig.Provider{}, errors.Wrap(err, "failed to register Gardener schema") | ||
} | ||
|
||
return kubeconfig.NewKubeconfigProvider(shootClient, | ||
dynamicKubeconfigAPI, | ||
namespace, | ||
int64(expirationTime.Seconds())), nil | ||
} | ||
|
||
func getRuntimeIDsFromStdin(cfg config.Config) []string { | ||
var runtimeIDs []string | ||
if cfg.InputType == config.InputTypeJSON { | ||
decoder := json.NewDecoder(os.Stdin) | ||
slog.Info("Reading runtimeIds from stdin") | ||
if err := decoder.Decode(&runtimeIDs); err != nil { | ||
log.Printf("Could not load list of RuntimeIds - %s", err) | ||
} | ||
} | ||
return runtimeIDs | ||
} | ||
|
||
func setupGardenerShootClient(kubeconfigPath, gardenerNamespace string) (gardener_types.ShootInterface, error) { | ||
restConfig, err := gardener.NewRestConfigFromFile(kubeconfigPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
gardenerClientSet, err := gardener_types.NewForConfig(restConfig) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
shootClient := gardenerClientSet.Shoots(gardenerNamespace) | ||
|
||
return shootClient, nil | ||
} |
This file contains 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,157 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/gardener/gardener/pkg/apis/core/v1beta1" | ||
gardener_types "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1" | ||
runtimev1 "github.com/kyma-project/infrastructure-manager/api/v1" | ||
config2 "github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/config" | ||
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/migration" | ||
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/runtime" | ||
"github.com/kyma-project/infrastructure-manager/pkg/config" | ||
"github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig" | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"log/slog" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
func NewMigration(migratorConfig config2.Config, converterConfig config.ConverterConfig, kubeconfigProvider kubeconfig.Provider, kcpClient client.Client, shootClient gardener_types.ShootInterface) (Migration, error) { | ||
|
||
outputWriter, err := migration.NewOutputWriter(migratorConfig.OutputPath) | ||
if err != nil { | ||
return Migration{}, err | ||
} | ||
|
||
return Migration{ | ||
runtimeMigrator: runtime.NewMigrator(migratorConfig, converterConfig, kubeconfigProvider, kcpClient), | ||
runtimeVerifier: runtime.NewVerifier(converterConfig, migratorConfig.OutputPath), | ||
kcpClient: kcpClient, | ||
shootClient: shootClient, | ||
outputWriter: outputWriter, | ||
isDryRun: migratorConfig.IsDryRun, | ||
}, nil | ||
} | ||
|
||
type Migration struct { | ||
runtimeMigrator runtime.Migrator | ||
runtimeVerifier runtime.Verifier | ||
kcpClient client.Client | ||
shootClient gardener_types.ShootInterface | ||
outputWriter migration.OutputWriter | ||
isDryRun bool | ||
} | ||
|
||
func (m Migration) Do(runtimeIDs []string) error { | ||
|
||
migratorContext, cancel := context.WithTimeout(context.Background(), contextTimeout) | ||
defer cancel() | ||
|
||
shootList, err := m.shootClient.List(migratorContext, v1.ListOptions{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
results := migration.NewMigratorResults(m.outputWriter.NewResultsDir) | ||
|
||
reportError := func(runtimeID, shootName string, msg string, err error) { | ||
var errorMsg string | ||
|
||
if err != nil { | ||
errorMsg = fmt.Sprintf("%s: %v", msg, err) | ||
} else { | ||
errorMsg = fmt.Sprintf(msg) | ||
} | ||
|
||
results.ErrorOccurred(runtimeID, shootName, errorMsg) | ||
slog.Error(errorMsg, "runtimeID", runtimeID) | ||
} | ||
|
||
reportValidationError := func(runtimeID, shootName string, msg string) { | ||
results.ValidationFailed(runtimeID, shootName) | ||
slog.Warn(msg, "runtimeID", runtimeID) | ||
} | ||
|
||
reportSuccess := func(runtimeID, shootName string, msg string) { | ||
results.OperationSucceeded(runtimeID, shootName) | ||
slog.Info(msg, "runtimeID", runtimeID) | ||
} | ||
|
||
for _, runtimeID := range runtimeIDs { | ||
slog.Info(fmt.Sprintf("Migrating runtime with ID: %s", runtimeID)) | ||
|
||
shoot := findShoot(runtimeID, shootList) | ||
if shoot == nil { | ||
reportError(runtimeID, "", "Failed to find shoot", nil) | ||
|
||
continue | ||
} | ||
|
||
runtime, err := m.runtimeMigrator.Do(migratorContext, *shoot) | ||
if err != nil { | ||
reportError(runtimeID, shoot.Name, "Failed to migrate runtime", err) | ||
|
||
continue | ||
} | ||
|
||
err = m.outputWriter.SaveRuntimeCR(runtime) | ||
if err != nil { | ||
reportError(runtimeID, shoot.Name, "Failed to save runtime CR", err) | ||
|
||
continue | ||
} | ||
|
||
shootComparisonResult, err := m.runtimeVerifier.Do(runtime, *shoot) | ||
if err != nil { | ||
reportError(runtimeID, shoot.Name, "Failed to verify runtime", err) | ||
|
||
continue | ||
} | ||
|
||
if !shootComparisonResult.IsEqual() { | ||
err = m.outputWriter.SaveComparisonResult(shootComparisonResult) | ||
if err != nil { | ||
reportError(runtimeID, shoot.Name, "Failed to save comparison result", err) | ||
} else { | ||
reportValidationError(runtimeID, shoot.Name, "Runtime CR can cause unwanted update in Gardener. Please verify the runtime CR.") | ||
} | ||
|
||
continue | ||
} | ||
|
||
if !m.isDryRun { | ||
err = m.applyRuntimeCR(runtime) | ||
if err != nil { | ||
reportError(runtimeID, shoot.Name, "Failed to apply Runtime CR", err) | ||
} | ||
|
||
continue | ||
} | ||
|
||
reportSuccess(runtimeID, shoot.Name, "Runtime processed successfully") | ||
} | ||
|
||
resultsFile, err := m.outputWriter.SaveMigrationResults(results) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
slog.Info(fmt.Sprintf("Migration completed. Successfully migrated runtimes: %d, Failed migrations: %d, Differences detected: %d", results.Succeeded, results.Failed, results.DifferenceDetected)) | ||
slog.Info(fmt.Sprintf("Migration results saved in: %s", resultsFile)) | ||
|
||
return nil | ||
} | ||
|
||
func findShoot(runtimeID string, shootList *v1beta1.ShootList) *v1beta1.Shoot { | ||
for _, shoot := range shootList.Items { | ||
if shoot.Annotations[runtimeIDAnnotation] == runtimeID { | ||
return &shoot | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (m Migration) applyRuntimeCR(runtime runtimev1.Runtime) error { | ||
// TODO: This method covers create scenario only, we should implement update as well | ||
return m.kcpClient.Create(context.Background(), &runtime) | ||
} |
This file contains 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 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
Oops, something went wrong.