Skip to content
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

Backup restore 4 #592

Merged
merged 11 commits into from
Jan 10, 2025
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
Loading