From b33be6f4591d3525f53ab746f7663dc2b1c2b03a Mon Sep 17 00:00:00 2001 From: andreaangiolillo Date: Tue, 17 Dec 2024 14:58:36 +0000 Subject: [PATCH] CLOUDP-285951: add support for flex clusters to "atlas backup restores start" --- docs/command/atlas-backups-restores-start.txt | 14 ++++- internal/cli/backup/restores/restores.go | 4 ++ internal/cli/backup/restores/start.go | 38 +++++++++++- internal/cli/backup/restores/start_test.go | 60 ++++++++++++++++--- 4 files changed, 104 insertions(+), 12 deletions(-) diff --git a/docs/command/atlas-backups-restores-start.txt b/docs/command/atlas-backups-restores-start.txt index f153630589..5013a092c7 100644 --- a/docs/command/atlas-backups-restores-start.txt +++ b/docs/command/atlas-backups-restores-start.txt @@ -17,7 +17,8 @@ Start a restore job for your project and cluster. If you create an automated or pointInTime restore job, Atlas removes all existing data on the target cluster prior to the restore. To use this command, you must authenticate with a user account or an API key with the Project Owner role. -Atlas supports this command only for M10+ clusters. +Atlas supports this command only for Flex and M10+ clusters. +Flex clusters support only automated restore jobs. Syntax ------ @@ -137,6 +138,17 @@ Examples --targetProjectId 1a2345b67c8e9a12f3456de7 +.. code-block:: + :copyable: false + + # Create an automated restore for a Flex Cluster: + atlas backup restore start automated \ + --clusterName myFlexSource \ + --snapshotId 5e7e00128f8ce03996a47179 \ + --targetClusterName myFlexCluster \ + --targetProjectId 1a2345b67c8e9a12f3456de7 + + .. code-block:: :copyable: false diff --git a/internal/cli/backup/restores/restores.go b/internal/cli/backup/restores/restores.go index 730ab072aa..17bc8742fe 100644 --- a/internal/cli/backup/restores/restores.go +++ b/internal/cli/backup/restores/restores.go @@ -19,6 +19,10 @@ import ( "github.com/spf13/cobra" ) +const ( + cannotUseNotFlexWithFlexApisErrorCode = "CANNOT_USE_NON_FLEX_CLUSTER_IN_FLEX_API" +) + func Builder() *cobra.Command { const use = "restores" cmd := &cobra.Command{ diff --git a/internal/cli/backup/restores/start.go b/internal/cli/backup/restores/start.go index 82d8101aa2..71ac59401d 100644 --- a/internal/cli/backup/restores/start.go +++ b/internal/cli/backup/restores/start.go @@ -60,14 +60,37 @@ func (opts *StartOpts) initStore(ctx context.Context) func() error { var startTemplate = "Restore job '{{.Id}}' successfully started\n" func (opts *StartOpts) Run() error { + r, err := opts.store.CreateRestoreFlexClusterJobs(opts.ConfigProjectID(), opts.clusterName, opts.newFlexBackupRestoreJobCreate()) + if err == nil { + return opts.Print(r) + } + + apiError, ok := admin.AsError(err) + if !ok { + return commonerrors.Check(err) + } + + if apiError.ErrorCode != cannotUseNotFlexWithFlexApisErrorCode { + return commonerrors.Check(err) + } + request := opts.newCloudProviderSnapshotRestoreJob() - r, err := opts.store.CreateRestoreJobs(opts.ConfigProjectID(), opts.clusterName, request) + restoreJob, err := opts.store.CreateRestoreJobs(opts.ConfigProjectID(), opts.clusterName, request) if err != nil { return commonerrors.Check(err) } - return opts.Print(r) + return opts.Print(restoreJob) +} + +func (opts *StartOpts) newFlexBackupRestoreJobCreate() *admin.FlexBackupRestoreJobCreate20241113 { + return &admin.FlexBackupRestoreJobCreate20241113{ + SnapshotId: opts.snapshotID, + TargetDeploymentItemName: opts.targetClusterName, + TargetProjectId: &opts.targetProjectID, + InstanceName: &opts.clusterName, + } } func (opts *StartOpts) newCloudProviderSnapshotRestoreJob() *admin.DiskBackupSnapshotRestoreJob { @@ -134,6 +157,7 @@ func markRequiredPointInTimeRestoreFlags(cmd *cobra.Command) error { return cmd.MarkFlagRequired(flag.TargetClusterName) } +// StartBuilder builds a cobra.Command that can run as: // atlas backup(s) restore(s) job(s) start . func StartBuilder() *cobra.Command { opts := new(StartOpts) @@ -142,7 +166,8 @@ func StartBuilder() *cobra.Command { Short: "Start a restore job for your project and cluster.", Long: `If you create an automated or pointInTime restore job, Atlas removes all existing data on the target cluster prior to the restore. -` + fmt.Sprintf("%s\n%s", fmt.Sprintf(usage.RequiredRole, "Project Owner"), "Atlas supports this command only for M10+ clusters."), +` + fmt.Sprintf("%s\n%s\n%s", fmt.Sprintf(usage.RequiredRole, "Project Owner"), "Atlas supports this command only for Flex and M10+ clusters.", + "Flex clusters support only automated restore jobs."), Args: require.ExactValidArgs(1), ValidArgs: []string{automatedRestore, downloadRestore, pointInTimeRestore}, Annotations: map[string]string{ @@ -156,6 +181,13 @@ func StartBuilder() *cobra.Command { --targetClusterName myDemo2 \ --targetProjectId 1a2345b67c8e9a12f3456de7 + # Create an automated restore for a Flex Cluster: + atlas backup restore start automated \ + --clusterName myFlexSource \ + --snapshotId 5e7e00128f8ce03996a47179 \ + --targetClusterName myFlexCluster \ + --targetProjectId 1a2345b67c8e9a12f3456de7 + # Create a point-in-time restore: atlas backup restore start pointInTime \ --clusterName myDemo \ diff --git a/internal/cli/backup/restores/start_test.go b/internal/cli/backup/restores/start_test.go index d1f852708b..4b8fc71898 100644 --- a/internal/cli/backup/restores/start_test.go +++ b/internal/cli/backup/restores/start_test.go @@ -21,6 +21,7 @@ import ( "github.com/golang/mock/gomock" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" + "github.com/stretchr/testify/require" atlasv2 "go.mongodb.org/atlas-sdk/v20241113002/admin" ) @@ -39,15 +40,44 @@ func TestStart_Run(t *testing.T) { targetProjectID: "1", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) + require.NoError(t, listOpts.Run()) + }) + + t.Run("Flex Cluster automated restore job", func(t *testing.T) { + listOpts := &StartOpts{ + store: mockStore, + method: automatedRestore, + clusterName: "Cluster0", + targetClusterName: "Cluster1", + targetProjectID: "1", } + + expectedFlex := &atlasv2.FlexBackupRestoreJob20241113{} + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(expectedFlex, nil). + Times(1) + + require.NoError(t, listOpts.Run()) }) t.Run(pointInTimeRestore, func(t *testing.T) { @@ -59,15 +89,22 @@ func TestStart_Run(t *testing.T) { targetProjectID: "1", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } + require.NoError(t, listOpts.Run()) }) t.Run(downloadRestore, func(t *testing.T) { @@ -77,14 +114,21 @@ func TestStart_Run(t *testing.T) { clusterName: "Cluster0", } + expectedError := &atlasv2.GenericOpenAPIError{} + expectedError.SetModel(atlasv2.ApiError{ErrorCode: cannotUseNotFlexWithFlexApisErrorCode}) + + mockStore. + EXPECT(). + CreateRestoreFlexClusterJobs(listOpts.ProjectID, "Cluster0", listOpts.newFlexBackupRestoreJobCreate()). + Return(nil, expectedError). + Times(1) + mockStore. EXPECT(). CreateRestoreJobs(listOpts.ProjectID, "Cluster0", listOpts.newCloudProviderSnapshotRestoreJob()). Return(expected, nil). Times(1) - if err := listOpts.Run(); err != nil { - t.Fatalf("Run() unexpected error: %v", err) - } + require.NoError(t, listOpts.Run()) }) }