Skip to content

Commit

Permalink
Merge pull request #592 from akgalwas/backup-restore-4
Browse files Browse the repository at this point in the history
Backup restore 4
  • Loading branch information
kyma-bot authored Jan 10, 2025
2 parents 7fc4837 + 122b907 commit f7af399
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 49 deletions.
124 changes: 119 additions & 5 deletions hack/runtime-migrator/cmd/backup/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@ import (
"context"
"fmt"
gardener_types "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1"
runtimev1 "github.com/kyma-project/infrastructure-manager/api/v1"
"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/shoot"
"github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig"
"github.com/pkg/errors"
rbacv1 "k8s.io/api/rbac/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"log/slog"
"sigs.k8s.io/controller-runtime/pkg/client"
"slices"
"time"
)

const (
timeoutK8sOperation = 20 * time.Second
fieldManagerName = "kim-backup"
)

type Backup struct {
Expand All @@ -24,10 +31,10 @@ type Backup struct {
kcpClient client.Client
outputWriter backup.OutputWriter
results backup.Results
cfg initialisation.Config
cfg initialisation.BackupConfig
}

func NewBackup(cfg initialisation.Config, kcpClient client.Client, shootClient gardener_types.ShootInterface) (Backup, error) {
func NewBackup(cfg initialisation.BackupConfig, kcpClient client.Client, shootClient gardener_types.ShootInterface) (Backup, error) {
outputWriter, err := backup.NewOutputWriter(cfg.OutputPath)
if err != nil {
return Backup{}, err
Expand Down Expand Up @@ -70,7 +77,15 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {
continue
}

runtimeBackup, err := backuper.Do(ctx, *shootToBackup, runtimeID)
runtimeClient, err := initialisation.GetRuntimeClient(ctx, b.kcpClient, runtimeID)
if err != nil {
errMsg := fmt.Sprintf("Failed to get kubernetes client for runtime: %v", err)
b.results.ErrorOccurred(runtimeID, shootToBackup.Name, errMsg)
slog.Error(errMsg, "runtimeID", runtimeID)

continue
}
runtimeBackup, err := backuper.Do(ctx, runtimeClient, *shootToBackup)
if err != nil {
errMsg := fmt.Sprintf("Failed to backup runtime: %v", err)
b.results.ErrorOccurred(runtimeID, shootToBackup.Name, errMsg)
Expand All @@ -81,7 +96,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)
b.results.OperationSucceeded(runtimeID, shootToBackup.Name, nil, false)

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

deprecatedCRBs, err := labelDeprecatedCRBs(ctx, runtimeClient)
if err != nil {
errMsg := fmt.Sprintf("Failed to deprecate Cluster Role Bindings: %v", err)
b.results.ErrorOccurred(runtimeID, shootToBackup.Name, errMsg)
slog.Error(errMsg, "runtimeID", runtimeID)

continue
}

if b.cfg.SetControlledByKim {
err := setControlledByKim(ctx, b.kcpClient, runtimeID)
if err != nil {
errMsg := fmt.Sprintf("Failed to set the rutnime to be controlled by KIM: %v", err)
b.results.ErrorOccurred(runtimeID, shootToBackup.Name, errMsg)
slog.Error(errMsg, "runtimeID", runtimeID)
}
}

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

resultsFile, err := b.outputWriter.SaveBackupResults(b.results)
Expand All @@ -108,3 +141,84 @@ func (b Backup) Do(ctx context.Context, runtimeIDs []string) error {

return nil
}

func labelDeprecatedCRBs(ctx context.Context, runtimeClient client.Client) ([]rbacv1.ClusterRoleBinding, error) {
var crbList rbacv1.ClusterRoleBindingList

listCtx, cancel := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancel()

selector, err := labels.Parse("reconciler.kyma-project.io/managed-by=reconciler,app=kyma")
if err != nil {
return nil, err
}

err = runtimeClient.List(listCtx, &crbList, &client.ListOptions{
LabelSelector: selector,
})

if err != nil {
return nil, err
}

deprecatedCRBs := slices.DeleteFunc(crbList.Items, func(clusterRoleBinding rbacv1.ClusterRoleBinding) bool {
if clusterRoleBinding.RoleRef.Kind != "ClusterRole" || clusterRoleBinding.RoleRef.Name != "cluster-admin" {
return true
}
// leave only cluster-admin CRBs where at least one subject is of a user type
if slices.ContainsFunc(clusterRoleBinding.Subjects, func(subject rbacv1.Subject) bool { return subject.Kind == rbacv1.UserKind }) {
return false
}
return true
})

patchCRB := func(clusterRoleBinding rbacv1.ClusterRoleBinding) error {
patchCtx, cancelPatch := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelPatch()

clusterRoleBinding.Kind = "ClusterRoleBinding"
clusterRoleBinding.APIVersion = "rbac.authorization.k8s.io/v1"
clusterRoleBinding.ManagedFields = nil

return runtimeClient.Patch(patchCtx, &clusterRoleBinding, client.Apply, &client.PatchOptions{
FieldManager: fieldManagerName,
})
}

for _, clusterRoleBinding := range deprecatedCRBs {
clusterRoleBinding.ObjectMeta.Labels["kyma-project.io/deprecation"] = "to-be-removed-soon"
err := patchCRB(clusterRoleBinding)
if err != nil {
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to update ClusterRoleBinding with deprecation label %s", clusterRoleBinding.Name))
}
}
}

return deprecatedCRBs, nil
}

func setControlledByKim(ctx context.Context, kcpClient client.Client, runtimeID string) error {
getCtx, cancelGet := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelGet()

key := types.NamespacedName{
Name: runtimeID,
Namespace: "kcp-system",
}
var runtime runtimev1.Runtime

err := kcpClient.Get(getCtx, key, &runtime, &client.GetOptions{})
if err != nil {
return err
}

runtime.Labels["kyma-project.io/controlled-by-provisioner"] = "false"

patchCtx, cancelPatch := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancelPatch()

return kcpClient.Patch(patchCtx, &runtime, client.Apply, &client.PatchOptions{
FieldManager: fieldManagerName,
})
}
8 changes: 4 additions & 4 deletions hack/runtime-migrator/cmd/backup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (

func main() {
slog.Info("Starting runtime-backuper")
cfg := initialisation.NewConfig()
cfg := initialisation.NewBackupConfig()

initialisation.PrintConfig(cfg)
initialisation.PrintBackupConfig(cfg)

opts := zap.Options{
Development: true,
Expand All @@ -30,7 +30,7 @@ func main() {
os.Exit(1)
}

kcpClient, err := initialisation.CreateKcpClient(&cfg)
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 @@ -43,7 +43,7 @@ func main() {
}

slog.Info("Reading runtimeIds from input file")
runtimeIds, err := initialisation.GetRuntimeIDsFromInputFile(cfg)
runtimeIds, err := initialisation.GetRuntimeIDsFromInputFile(cfg.Config)
if err != nil {
slog.Error("Failed to read runtime Ids from input", slog.Any("error", err))
os.Exit(1)
Expand Down
9 changes: 7 additions & 2 deletions hack/runtime-migrator/cmd/migration/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/shoot"
"log/slog"
"time"

gardener_types "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1"
runtimev1 "github.com/kyma-project/infrastructure-manager/api/v1"
Expand All @@ -26,6 +27,10 @@ type Migration struct {
isDryRun bool
}

const (
timeoutK8sOperation = 20 * time.Second
)

func NewMigration(migratorConfig initialisation.Config, converterConfig config.ConverterConfig, auditLogConfig auditlogs.Configuration, kubeconfigProvider kubeconfig.Provider, kcpClient client.Client, shootClient gardener_types.ShootInterface) (Migration, error) {

outputWriter, err := migration.NewOutputWriter(migratorConfig.OutputPath)
Expand All @@ -44,7 +49,7 @@ func NewMigration(migratorConfig initialisation.Config, converterConfig config.C
}

func (m Migration) Do(ctx context.Context, runtimeIDs []string) error {
listCtx, cancel := context.WithTimeout(ctx, initialisation.TimeoutK8sOperation)
listCtx, cancel := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancel()

shootList, err := m.shootClient.List(listCtx, v1.ListOptions{})
Expand Down Expand Up @@ -92,7 +97,7 @@ func (m Migration) Do(ctx context.Context, runtimeIDs []string) error {
return
}

migrationCtx, cancel := context.WithTimeout(ctx, initialisation.TimeoutK8sOperation)
migrationCtx, cancel := context.WithTimeout(ctx, timeoutK8sOperation)
defer cancel()

runtime, err := m.runtimeMigrator.Do(migrationCtx, *shootToMigrate)
Expand Down
1 change: 1 addition & 0 deletions hack/runtime-migrator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/kyma-project/infrastructure-manager/hack/shoot-comparator v0.0.0-20241023155010-55a6abeb1690
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
k8s.io/api v0.32.0
k8s.io/apiextensions-apiserver v0.31.3
k8s.io/apimachinery v0.32.0
Expand Down
20 changes: 11 additions & 9 deletions hack/runtime-migrator/internal/backup/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/gardener/gardener/pkg/apis/core/v1beta1"
authenticationv1alpha1 "github.com/gardener/oidc-webhook-authenticator/apis/authentication/v1alpha1"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/initialisation"
"github.com/kyma-project/infrastructure-manager/hack/runtime-migrator-app/internal/shoot"
"github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig"
"github.com/pkg/errors"
rbacv1 "k8s.io/api/rbac/v1"
Expand Down Expand Up @@ -37,13 +38,8 @@ type RuntimeBackup struct {
OIDCConfig []authenticationv1alpha1.OpenIDConnect
}

func (b Backuper) Do(ctx context.Context, shoot v1beta1.Shoot, runtimeID string) (RuntimeBackup, error) {
runtimeClient, err := initialisation.GetRuntimeClient(ctx, b.kcpClient, runtimeID)
if err != nil {
return RuntimeBackup{}, err
}

crbs, err := b.getCRBs(ctx, runtimeClient)
func (b Backuper) Do(ctx context.Context, runtimeClient client.Client, shoot v1beta1.Shoot) (RuntimeBackup, error) {
crbs, err := b.getAllCRBs(ctx, runtimeClient)
if err != nil {
return RuntimeBackup{}, errors.Wrap(err, "failed to get Cluster Role Bindings")
}
Expand Down Expand Up @@ -98,7 +94,13 @@ func (b Backuper) getShootForPatch(shootFromGardener v1beta1.Shoot) v1beta1.Shoo
Services: shootFromGardener.Spec.Networking.Services,
},
// TODO: consider if we need to do the backup selectively (workers)
Provider: shootFromGardener.Spec.Provider,
Provider: v1beta1.Provider{
Type: shootFromGardener.Spec.Provider.Type,
ControlPlaneConfig: shootFromGardener.Spec.Provider.ControlPlaneConfig,
InfrastructureConfig: shootFromGardener.Spec.Provider.InfrastructureConfig,
Workers: shoot.FilterOutFields(shootFromGardener.Spec.Provider.Workers),
WorkersSettings: shootFromGardener.Spec.Provider.WorkersSettings,
},
Purpose: shootFromGardener.Spec.Purpose,
Region: shootFromGardener.Spec.Region,
Resources: shootFromGardener.Spec.Resources,
Expand All @@ -108,7 +110,7 @@ func (b Backuper) getShootForPatch(shootFromGardener v1beta1.Shoot) v1beta1.Shoo
}
}

func (b Backuper) getCRBs(ctx context.Context, runtimeClient client.Client) ([]rbacv1.ClusterRoleBinding, error) {
func (b Backuper) getAllCRBs(ctx context.Context, runtimeClient client.Client) ([]rbacv1.ClusterRoleBinding, error) {
var crbList rbacv1.ClusterRoleBindingList

listCtx, cancel := context.WithTimeout(ctx, b.timeoutK8sOperation)
Expand Down
31 changes: 21 additions & 10 deletions hack/runtime-migrator/internal/backup/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package backup

import (
"fmt"
v12 "k8s.io/api/rbac/v1"
)

type StatusType string
Expand All @@ -12,11 +13,13 @@ const (
)

type RuntimeResult struct {
RuntimeID string `json:"runtimeId"`
ShootName string `json:"shootName"`
Status StatusType `json:"status"`
ErrorMessage string `json:"errorMessage,omitempty"`
BackupDirPath string `json:"backupDirPath,omitempty"`
RuntimeID string `json:"runtimeId"`
ShootName string `json:"shootName"`
Status StatusType `json:"status"`
ErrorMessage string `json:"errorMessage,omitempty"`
BackupDirPath string `json:"backupDirPath,omitempty"`
DeprecatedCRBs []string `json:"deprecatedCRBs,omitempty"`
SetControlledByKIM bool `json:"setControlledByKIM"`
}

type Results struct {
Expand Down Expand Up @@ -45,12 +48,20 @@ func (br *Results) ErrorOccurred(runtimeID, shootName string, errorMsg string) {
br.Results = append(br.Results, result)
}

func (br *Results) OperationSucceeded(runtimeID string, shootName string) {
func (br *Results) OperationSucceeded(runtimeID string, shootName string, deprecatedCRBs []v12.ClusterRoleBinding, setControlledByKIM bool) {

var deprecatedCRBsString []string
for _, crb := range deprecatedCRBs {
deprecatedCRBsString = append(deprecatedCRBsString, crb.Name)
}

result := RuntimeResult{
RuntimeID: runtimeID,
ShootName: shootName,
Status: StatusSuccess,
BackupDirPath: br.getBackupDirPath(runtimeID),
RuntimeID: runtimeID,
ShootName: shootName,
Status: StatusSuccess,
BackupDirPath: br.getBackupDirPath(runtimeID),
DeprecatedCRBs: deprecatedCRBsString,
SetControlledByKIM: setControlledByKIM,
}

br.Succeeded++
Expand Down
27 changes: 27 additions & 0 deletions hack/runtime-migrator/internal/initialisation/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,33 @@ func GetRuntimeIDsFromInputFile(cfg Config) ([]string, error) {
return runtimeIDs, err
}

type BackupConfig struct {
Config
SetControlledByKim bool
}

func NewBackupConfig() BackupConfig {
backupConfig := BackupConfig{}

flag.StringVar(&backupConfig.KcpKubeconfigPath, "kcp-kubeconfig-path", "/path/to/kcp/kubeconfig", "Path to the Kubeconfig file of KCP cluster.")
flag.StringVar(&backupConfig.GardenerKubeconfigPath, "gardener-kubeconfig-path", "/path/to/gardener/kubeconfig", "Kubeconfig file for Gardener cluster.")
flag.StringVar(&backupConfig.GardenerProjectName, "gardener-project-name", "gardener-project-name", "Name of the Gardener project.")
flag.StringVar(&backupConfig.OutputPath, "output-path", "/tmp/", "Path where generated yamls will be saved. Directory has to exist.")
flag.BoolVar(&backupConfig.IsDryRun, "dry-run", true, "Dry-run flag. Has to be set to 'false' otherwise it will not apply the Custom Resources on the KCP cluster.")
flag.StringVar(&backupConfig.InputType, "input-type", InputTypeJSON, "Type of input to be used. Possible values: **txt** (see the example hack/runtime-migrator/input/runtimeids_sample.txt), and **json** (see the example hack/runtime-migrator/input/runtimeids_sample.json).")
flag.StringVar(&backupConfig.InputFilePath, "input-file-path", "/path/to/input/file", "Path to the input file containing RuntimeCRs to be migrated.")
flag.BoolVar(&backupConfig.SetControlledByKim, "set-controlled-by-kim", false, "Flag determining whether Runtime CR should be modified to be controlled by KIM.")

flag.Parse()

return backupConfig
}

func PrintBackupConfig(cfg BackupConfig) {
PrintConfig(cfg.Config)
log.Println("set-controlled-by-kim:", cfg.SetControlledByKim)
}

type RestoreConfig struct {
Config
BackupDir string
Expand Down
Loading

0 comments on commit f7af399

Please sign in to comment.