Skip to content

Commit

Permalink
Merge pull request #591 from akgalwas/backup-restore-3
Browse files Browse the repository at this point in the history
Implement backup and restore for Cluster Role Bindings and OIDC
  • Loading branch information
kyma-bot authored Jan 9, 2025
2 parents 81eff0e + f475a34 commit cc31c47
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 105 deletions.
22 changes: 12 additions & 10 deletions hack/runtime-migrator/cmd/backup/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,35 @@ import (
"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"
"time"
)

const (
timeoutK8sOperation = 20 * time.Second
expirationTime = 60 * time.Minute
)

type Backup struct {
shootClient gardener_types.ShootInterface
kubeconfigProvider kubeconfig.Provider
kcpClient client.Client
outputWriter backup.OutputWriter
results backup.Results
cfg initialisation.Config
}

func NewBackup(cfg initialisation.Config, kubeconfigProvider kubeconfig.Provider, shootClient gardener_types.ShootInterface) (Backup, error) {
func NewBackup(cfg initialisation.Config, kcpClient client.Client, shootClient gardener_types.ShootInterface) (Backup, error) {
outputWriter, err := backup.NewOutputWriter(cfg.OutputPath)
if err != nil {
return Backup{}, err
}

return Backup{
shootClient: shootClient,
kubeconfigProvider: kubeconfigProvider,
outputWriter: outputWriter,
results: backup.NewBackupResults(outputWriter.NewResultsDir),
cfg: cfg,
shootClient: shootClient,
kcpClient: kcpClient,
outputWriter: outputWriter,
results: backup.NewBackupResults(outputWriter.NewResultsDir),
cfg: cfg,
}, nil
}

Expand All @@ -50,7 +51,7 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {
return err
}

backuper := backup.NewBackuper(b.cfg.IsDryRun, b.kubeconfigProvider)
backuper := backup.NewBackuper(b.cfg.IsDryRun, b.kcpClient, timeoutK8sOperation)

for _, runtimeID := range runtimeIDs {
shootToBackup, err := shoot.Fetch(ctx, shootList, b.shootClient, runtimeID)
Expand All @@ -69,7 +70,7 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {
continue
}

runtimeBackup, err := backuper.Do(ctx, *shootToBackup)
runtimeBackup, err := backuper.Do(ctx, *shootToBackup, runtimeID)
if err != nil {
errMsg := fmt.Sprintf("Failed to backup runtime: %v", err)
b.results.ErrorOccurred(runtimeID, shootToBackup.Name, errMsg)
Expand All @@ -80,6 +81,7 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {

if b.cfg.IsDryRun {
slog.Info("Runtime processed successfully (dry-run)", "runtimeID", runtimeID)
b.results.OperationSucceeded(runtimeID, shootToBackup.Name)

continue
}
Expand All @@ -92,7 +94,7 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {
continue
}

slog.Info("Runtime backup created successfully successfully", "runtimeID", runtimeID)
slog.Info("Runtime backup created successfully", "runtimeID", runtimeID)
b.results.OperationSucceeded(runtimeID, shootToBackup.Name)
}

Expand Down
10 changes: 5 additions & 5 deletions hack/runtime-migrator/cmd/backup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ func main() {

gardenerNamespace := fmt.Sprintf("garden-%s", cfg.GardenerProjectName)

kubeconfigProvider, err := initialisation.SetupKubernetesKubeconfigProvider(cfg.GardenerKubeconfigPath, gardenerNamespace, expirationTime)
shootClient, _, err := initialisation.SetupGardenerShootClients(cfg.GardenerKubeconfigPath, gardenerNamespace)
if err != nil {
slog.Error(fmt.Sprintf("Failed to create kubeconfig provider: %v", err))
slog.Error("Failed to setup Gardener shoot client", slog.Any("error", err))
os.Exit(1)
}

shootClient, _, err := initialisation.SetupGardenerShootClients(cfg.GardenerKubeconfigPath, gardenerNamespace)
kcpClient, err := initialisation.CreateKcpClient(&cfg)
if err != nil {
slog.Error("Failed to setup Gardener shoot client", slog.Any("error", err))
slog.Error("Failed to create kcp client", slog.Any("error", err))
os.Exit(1)
}

backup, err := NewBackup(cfg, kubeconfigProvider, shootClient)
backup, err := NewBackup(cfg, kcpClient, shootClient)
if err != nil {
slog.Error("Failed to initialize backup", slog.Any("error", err))
os.Exit(1)
Expand Down
6 changes: 3 additions & 3 deletions hack/runtime-migrator/cmd/restore/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ func main() {

gardenerNamespace := fmt.Sprintf("garden-%s", cfg.GardenerProjectName)

kubeconfigProvider, err := initialisation.SetupKubernetesKubeconfigProvider(cfg.GardenerKubeconfigPath, gardenerNamespace, expirationTime)
_, err := initialisation.SetupKubernetesKubeconfigProvider(cfg.GardenerKubeconfigPath, gardenerNamespace, expirationTime)
if err != nil {
slog.Error(fmt.Sprintf("Failed to create kubeconfig provider: %v", err))
os.Exit(1)
}

_, err = initialisation.CreateKcpClient(&cfg.Config)
kcpClient, err := initialisation.CreateKcpClient(&cfg.Config)
if err != nil {
slog.Error("Failed to create kcp client", slog.Any("error", err))
os.Exit(1)
Expand All @@ -42,7 +42,7 @@ func main() {
os.Exit(1)
}

restore, err := NewRestore(cfg, kubeconfigProvider, shootClient, dynamicGardenerClient)
restore, err := NewRestore(cfg, kcpClient, shootClient, dynamicGardenerClient)
if err != nil {
slog.Error("Failed to setup Gardener shoot client", slog.Any("error", err))
os.Exit(1)
Expand Down
146 changes: 132 additions & 14 deletions hack/runtime-migrator/cmd/restore/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"fmt"
"github.com/gardener/gardener/pkg/apis/core/v1beta1"
gardener_types "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1"
authenticationv1alpha1 "github.com/gardener/oidc-webhook-authenticator/apis/authentication/v1alpha1"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/backup"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/initialisation"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/restore"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/shoot"
"github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig"
v12 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"log/slog"
Expand All @@ -24,15 +27,15 @@ const (
type Restore struct {
shootClient gardener_types.ShootInterface
dynamicGardenerClient client.Client
kubeconfigProvider kubeconfig.Provider
kcpClient client.Client
outputWriter restore.OutputWriter
results restore.Results
cfg initialisation.RestoreConfig
}

const fieldManagerName = "kim"
const fieldManagerName = "kim-restore"

func NewRestore(cfg initialisation.RestoreConfig, kubeconfigProvider kubeconfig.Provider, shootClient gardener_types.ShootInterface, dynamicGardenerClient client.Client) (Restore, error) {
func NewRestore(cfg initialisation.RestoreConfig, kcpClient client.Client, shootClient gardener_types.ShootInterface, dynamicGardenerClient client.Client) (Restore, error) {
outputWriter, err := restore.NewOutputWriter(cfg.OutputPath)
if err != nil {
return Restore{}, err
Expand All @@ -41,7 +44,7 @@ func NewRestore(cfg initialisation.RestoreConfig, kubeconfigProvider kubeconfig.
return Restore{
shootClient: shootClient,
dynamicGardenerClient: dynamicGardenerClient,
kubeconfigProvider: kubeconfigProvider,
kcpClient: kcpClient,
outputWriter: outputWriter,
results: restore.NewRestoreResults(outputWriter.NewResultsDir),
cfg: cfg,
Expand All @@ -57,7 +60,7 @@ func (r Restore) Do(ctx context.Context, runtimeIDs []string) error {
return err
}

restorer := restore.NewRestorer(r.cfg.BackupDir)
restorer := restore.NewBackupReader(r.cfg.BackupDir, r.cfg.RestoreCRB, r.cfg.RestoreOIDC)

for _, runtimeID := range runtimeIDs {
currentShoot, err := shoot.Fetch(ctx, shootList, r.shootClient, runtimeID)
Expand All @@ -77,33 +80,40 @@ func (r Restore) Do(ctx context.Context, runtimeIDs []string) error {
continue
}

shootToRestore, err := restorer.Do(runtimeID, currentShoot.Name)
objectsToRestore, err := restorer.Do(runtimeID, currentShoot.Name)
if err != nil {
errMsg := fmt.Sprintf("Failed to restore runtime: %v", err)
errMsg := fmt.Sprintf("Failed to read runtime from backup directory: %v", err)
r.results.ErrorOccurred(runtimeID, currentShoot.Name, errMsg)
slog.Error(errMsg, "runtimeID", runtimeID)

continue
}

if currentShoot.Generation > objectsToRestore.OriginalShoot.Generation+1 {
slog.Warn("Verify the current state of the system. Restore should be performed manually, as the backup may overwrite user's changes.", "runtimeID", runtimeID)
r.results.AutomaticRestoreImpossible(runtimeID, currentShoot.Name)

continue
}

if r.cfg.IsDryRun {
slog.Info("Runtime processed successfully (dry-run)", "runtimeID", runtimeID)
r.results.OperationSucceeded(runtimeID, currentShoot.Name)
r.results.OperationSucceeded(runtimeID, currentShoot.Name, nil, nil)

continue
}

err = r.applyResources(ctx, shootToRestore)
appliedCRBs, appliedOIDC, err := r.applyResources(ctx, objectsToRestore, runtimeID)
if err != nil {
errMsg := fmt.Sprintf("Failed to restore runtime: %v", err)
errMsg := fmt.Sprintf("Failed to apply resources: %v", err)
r.results.ErrorOccurred(runtimeID, currentShoot.Name, errMsg)
slog.Error(errMsg, "runtimeID", runtimeID)

continue
}

slog.Info("Runtime restore performed successfully", "runtimeID", runtimeID)
r.results.OperationSucceeded(runtimeID, currentShoot.Name)
r.results.OperationSucceeded(runtimeID, currentShoot.Name, appliedCRBs, appliedOIDC)
}

resultsFile, err := r.outputWriter.SaveRestoreResults(r.results)
Expand All @@ -117,12 +127,120 @@ func (r Restore) Do(ctx context.Context, runtimeIDs []string) error {
return nil
}

func (r Restore) applyResources(ctx context.Context, shootToRestore v1beta1.Shoot) error {
func (r Restore) applyResources(ctx context.Context, objectsToRestore backup.RuntimeBackup, runtimeID string) ([]v12.ClusterRoleBinding, []authenticationv1alpha1.OpenIDConnect, error) {
err := r.applyShoot(ctx, objectsToRestore.ShootForPatch)
if err != nil {
return nil, nil, err
}

clusterClient, err := initialisation.GetRuntimeClient(ctx, r.kcpClient, runtimeID)
if err != nil {
return nil, nil, err
}

appliedCRBs, err := r.applyCRBs(ctx, clusterClient, objectsToRestore.ClusterRoleBindings)
if err != nil {
return nil, nil, err
}

appliedOIDC, err := r.applyOIDC(ctx, clusterClient, objectsToRestore.OIDCConfig)
if err != nil {
return nil, nil, err
}

return appliedCRBs, appliedOIDC, nil
}

func (r Restore) applyShoot(ctx context.Context, shoot v1beta1.Shoot) error {
patchCtx, cancel := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancel()

return r.dynamicGardenerClient.Patch(patchCtx, &shootToRestore, client.Apply, &client.PatchOptions{
return r.dynamicGardenerClient.Patch(patchCtx, &shoot, client.Apply, &client.PatchOptions{
FieldManager: fieldManagerName,
Force: ptr.To(true),
})
}

func (r Restore) applyCRBs(ctx context.Context, clusterClient client.Client, crbs []v12.ClusterRoleBinding) ([]v12.ClusterRoleBinding, error) {
appliedCRBs := make([]v12.ClusterRoleBinding, 0)

for _, crb := range crbs {
key := client.ObjectKey{
Name: crb.Name,
Namespace: crb.Namespace,
}
applied, err := applyCRBIfDoesntExist(ctx, key, &crb, clusterClient)
if err != nil {
return nil, err
}

if applied {
appliedCRBs = append(appliedCRBs, crb)
}
}

return appliedCRBs, nil
}

func (r Restore) applyOIDC(ctx context.Context, clusterClient client.Client, oidcConfigs []authenticationv1alpha1.OpenIDConnect) ([]authenticationv1alpha1.OpenIDConnect, error) {
appliedOIDCs := make([]authenticationv1alpha1.OpenIDConnect, 0)

for _, oidc := range oidcConfigs {
key := client.ObjectKey{
Name: oidc.Name,
Namespace: oidc.Namespace,
}
applied, err := applyOIDCIfDoesntExist(ctx, key, &oidc, clusterClient)
if err != nil {
return nil, err
}

if applied {
appliedOIDCs = append(appliedOIDCs, oidc)
}
}

return appliedOIDCs, nil
}

func applyCRBIfDoesntExist(ctx context.Context, key client.ObjectKey, object *v12.ClusterRoleBinding, clusterClient client.Client) (bool, error) {
getCtx, cancelGet := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelGet()

var existingObject v12.ClusterRoleBinding

err := clusterClient.Get(getCtx, key, &existingObject, &client.GetOptions{})
if err == nil {
return false, nil
}

if err != nil && !errors.IsNotFound(err) {
return false, err
}

createCtx, cancelCreate := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelCreate()

return true, clusterClient.Create(createCtx, object, &client.CreateOptions{})
}

func applyOIDCIfDoesntExist(ctx context.Context, key client.ObjectKey, object *authenticationv1alpha1.OpenIDConnect, clusterClient client.Client) (bool, error) {
getCtx, cancelGet := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelGet()

var existingObject authenticationv1alpha1.OpenIDConnect

err := clusterClient.Get(getCtx, key, &existingObject, &client.GetOptions{})
if err == nil {
return false, nil
}
slog.Error(err.Error())
if err != nil && !errors.IsNotFound(err) {
return false, err
}

createCtx, cancelCreate := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelCreate()

return true, clusterClient.Create(createCtx, object, &client.CreateOptions{})
}
Loading

0 comments on commit cc31c47

Please sign in to comment.