Skip to content

Commit baeee57

Browse files
CLOUDP-285950: add support for flex clusters to atlas backup snaphots download (#3488)
1 parent 2fd2432 commit baeee57

File tree

7 files changed

+312
-2
lines changed

7 files changed

+312
-2
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
.. _atlas-backups-snapshots-download:
2+
3+
================================
4+
atlas backups snapshots download
5+
================================
6+
7+
.. default-domain:: mongodb
8+
9+
.. contents:: On this page
10+
:local:
11+
:backlinks: none
12+
:depth: 1
13+
:class: singlecol
14+
15+
Download one snapshot for the specified flex cluster.
16+
17+
You can download a snapshot for an Atlas Flex cluster.
18+
To use this command, you must authenticate with a user account or an API key with the Project Owner role.
19+
Atlas supports this command only for Flex clusters.
20+
21+
Syntax
22+
------
23+
24+
.. code-block::
25+
:caption: Command Syntax
26+
27+
atlas backups snapshots download <snapshotId> [options]
28+
29+
.. Code end marker, please don't delete this comment
30+
31+
Arguments
32+
---------
33+
34+
.. list-table::
35+
:header-rows: 1
36+
:widths: 20 10 10 60
37+
38+
* - Name
39+
- Type
40+
- Required
41+
- Description
42+
* - snapshotId
43+
- string
44+
- true
45+
- Unique 24-hexadecimal digit string that identifies the snapshot to download.
46+
47+
Options
48+
-------
49+
50+
.. list-table::
51+
:header-rows: 1
52+
:widths: 20 10 10 60
53+
54+
* - Name
55+
- Type
56+
- Required
57+
- Description
58+
* - --clusterName
59+
- string
60+
- true
61+
- Name of the cluster. To learn more, see https://dochub.mongodb.org/core/create-cluster-api.
62+
* - -h, --help
63+
-
64+
- false
65+
- help for download
66+
* - --out
67+
- string
68+
- false
69+
- Output file name. This value defaults to the Snapshot id.
70+
* - --projectId
71+
- string
72+
- false
73+
- Hexadecimal string that identifies the project to use. This option overrides the settings in the configuration file or environment variable.
74+
75+
Inherited Options
76+
-----------------
77+
78+
.. list-table::
79+
:header-rows: 1
80+
:widths: 20 10 10 60
81+
82+
* - Name
83+
- Type
84+
- Required
85+
- Description
86+
* - -P, --profile
87+
- string
88+
- false
89+
- Name of the profile to use from your configuration file. To learn about profiles for the Atlas CLI, see https://dochub.mongodb.org/core/atlas-cli-save-connection-settings.
90+
91+
Output
92+
------
93+
94+
If the command succeeds, the CLI returns output similar to the following sample. Values in brackets represent your values.
95+
96+
.. code-block::
97+
98+
Snapshot '<Name>' downloaded.
99+
100+

docs/command/atlas-backups-snapshots.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Related Commands
5252
* :ref:`atlas-backups-snapshots-create` - Create a backup snapshot for your project and cluster.
5353
* :ref:`atlas-backups-snapshots-delete` - Remove the specified backup snapshot.
5454
* :ref:`atlas-backups-snapshots-describe` - Return the details for the specified snapshot for your project.
55+
* :ref:`atlas-backups-snapshots-download` - Download one snapshot for the specified flex cluster.
5556
* :ref:`atlas-backups-snapshots-list` - Return all cloud backup snapshots for your project and cluster.
5657
* :ref:`atlas-backups-snapshots-watch` - Watch the specified snapshot in your project until it becomes available.
5758

@@ -62,6 +63,7 @@ Related Commands
6263
create </command/atlas-backups-snapshots-create>
6364
delete </command/atlas-backups-snapshots-delete>
6465
describe </command/atlas-backups-snapshots-describe>
66+
download </command/atlas-backups-snapshots-download>
6567
list </command/atlas-backups-snapshots-list>
6668
watch </command/atlas-backups-snapshots-watch>
6769

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright 2024 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package snapshots
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
"io"
22+
"net/http"
23+
"strings"
24+
25+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli"
26+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require"
27+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
28+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
29+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store"
30+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
31+
"github.com/spf13/afero"
32+
"github.com/spf13/cobra"
33+
atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin"
34+
)
35+
36+
type DownloadOpts struct {
37+
cli.ProjectOpts
38+
cli.DownloaderOpts
39+
cli.OutputOpts
40+
store store.SnapshotsDownloader
41+
clusterName string
42+
id string
43+
}
44+
45+
func (opts *DownloadOpts) initStore(ctx context.Context) func() error {
46+
return func() error {
47+
var err error
48+
opts.store, err = store.New(store.AuthenticatedPreset(config.Default()), store.WithContext(ctx))
49+
return err
50+
}
51+
}
52+
53+
var downloadTemplate = "Snapshot '%s' downloaded.\n"
54+
var errEmptyURL = errors.New("'snapshotUrl' is empty")
55+
var errExtNotSupported = errors.New("only the '.tgz' extension is supported")
56+
57+
func (opts *DownloadOpts) Run() error {
58+
r, err := opts.store.DownloadFlexClusterSnapshot(opts.ConfigProjectID(), opts.clusterName, opts.newFlexBackupSnapshotDownloadCreate())
59+
if err != nil {
60+
return err
61+
}
62+
63+
return opts.Download(r.SnapshotUrl)
64+
}
65+
66+
func (opts *DownloadOpts) newFlexBackupSnapshotDownloadCreate() *atlasv2.FlexBackupSnapshotDownloadCreate20241113 {
67+
return &atlasv2.FlexBackupSnapshotDownloadCreate20241113{
68+
SnapshotId: opts.id,
69+
}
70+
}
71+
72+
func (opts *DownloadOpts) Download(url *string) error {
73+
if url == nil {
74+
return errEmptyURL
75+
}
76+
77+
w, err := opts.NewWriteCloser()
78+
if err != nil {
79+
return err
80+
}
81+
defer w.Close()
82+
83+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, *url, nil)
84+
if err != nil {
85+
return err
86+
}
87+
88+
resp, err := http.DefaultClient.Do(req)
89+
if err != nil {
90+
return err
91+
}
92+
93+
defer resp.Body.Close()
94+
if err != nil {
95+
_ = opts.OnError(w)
96+
return err
97+
}
98+
99+
if _, err := io.Copy(w, resp.Body); err != nil {
100+
_ = opts.OnError(w)
101+
return err
102+
}
103+
104+
fmt.Printf(downloadTemplate, opts.Out)
105+
return nil
106+
}
107+
108+
func (opts *DownloadOpts) initDefaultOut() error {
109+
if opts.Out == "" {
110+
opts.Out = opts.id + ".tgz"
111+
} else if !strings.Contains(opts.Out, ".tgz") {
112+
return errExtNotSupported
113+
}
114+
115+
return nil
116+
}
117+
118+
// DownloadBuilder builds a cobra.Command that can run as:
119+
// atlas backup snapshots download snapshotId --clusterName string [--projectId string] [--out string].
120+
func DownloadBuilder() *cobra.Command {
121+
opts := &DownloadOpts{}
122+
opts.Fs = afero.NewOsFs()
123+
cmd := &cobra.Command{
124+
Use: "download <snapshotId>",
125+
Short: "Download one snapshot for the specified flex cluster.",
126+
Long: `You can download a snapshot for an Atlas Flex cluster.
127+
` + fmt.Sprintf("%s\n%s", fmt.Sprintf(usage.RequiredRole, "Project Owner"), "Atlas supports this command only for Flex clusters."),
128+
Args: require.ExactArgs(1),
129+
Annotations: map[string]string{
130+
"snapshotIdDesc": "Unique 24-hexadecimal digit string that identifies the snapshot to download.",
131+
"output": downloadTemplate,
132+
},
133+
PreRunE: func(cmd *cobra.Command, _ []string) error {
134+
return opts.PreRunE(
135+
opts.ValidateProjectID,
136+
opts.initStore(cmd.Context()),
137+
opts.InitOutput(cmd.OutOrStdout(), createTemplate),
138+
)
139+
},
140+
RunE: func(_ *cobra.Command, args []string) error {
141+
opts.id = args[0]
142+
if err := opts.initDefaultOut(); err != nil {
143+
return err
144+
}
145+
return opts.Run()
146+
},
147+
}
148+
cmd.Flags().StringVar(&opts.clusterName, flag.ClusterName, "", usage.ClusterName)
149+
cmd.Flags().StringVar(&opts.Out, flag.Out, "", usage.SnapshotOut)
150+
151+
opts.AddProjectOptsFlags(cmd)
152+
153+
_ = cmd.MarkFlagRequired(flag.ClusterName)
154+
155+
return cmd
156+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2024 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package snapshots
16+
17+
import (
18+
"testing"
19+
20+
"github.com/golang/mock/gomock"
21+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks"
22+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer"
23+
"github.com/spf13/afero"
24+
"github.com/stretchr/testify/require"
25+
atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin"
26+
)
27+
28+
func TestSnapshotDownloadOpts_Run(t *testing.T) {
29+
ctrl := gomock.NewController(t)
30+
mockStore := mocks.NewMockSnapshotsDownloader(ctrl)
31+
32+
opts := &DownloadOpts{
33+
id: "test.tgz",
34+
store: mockStore,
35+
clusterName: "test",
36+
}
37+
opts.Out = opts.id
38+
opts.Fs = afero.NewMemMapFs()
39+
40+
expected := &atlasv2.FlexBackupRestoreJob20241113{
41+
SnapshotUrl: pointer.Get("test.tgz"),
42+
}
43+
44+
mockStore.
45+
EXPECT().
46+
DownloadFlexClusterSnapshot(opts.ConfigProjectID(), opts.clusterName, opts.newFlexBackupSnapshotDownloadCreate()).
47+
Return(expected, nil).
48+
Times(1)
49+
50+
require.Error(t, opts.Run(), errEmptyURL.Error())
51+
}

internal/cli/backup/snapshots/snapshots.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func Builder() *cobra.Command {
3737
DescribeBuilder(),
3838
WatchBuilder(),
3939
DeleteBuilder(),
40+
DownloadBuilder(),
4041
)
4142

4243
return cmd

internal/store/cloud_provider_backup.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ func (s *Store) FlexClusterSnapshots(opts *atlasv2.ListFlexBackupsApiParams) (*a
177177
return result, err
178178
}
179179

180-
// DownloadFlexClusterSnapshots encapsulates the logic to manage different cloud providers.
181-
func (s *Store) DownloadFlexClusterSnapshots(groupID, name string, flexBackupSnapshotDownloadCreate20241113 *atlasv2.FlexBackupSnapshotDownloadCreate20241113) (*atlasv2.FlexBackupRestoreJob20241113, error) {
180+
func (s *Store) DownloadFlexClusterSnapshot(groupID, name string, flexBackupSnapshotDownloadCreate20241113 *atlasv2.FlexBackupSnapshotDownloadCreate20241113) (*atlasv2.FlexBackupRestoreJob20241113, error) {
182181
if s.service == config.CloudGovService {
183182
return nil, fmt.Errorf("%w: %s", errUnsupportedService, s.service)
184183
}

internal/usage/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ dbName and collection are required only for built-in roles.`
9292
ForceFile = "Flag that indicates whether to overwrite the destination file."
9393
Email = "Email address for the user."
9494
LogOut = "Output file name. This value defaults to the log name."
95+
SnapshotOut = "Output file name. This value defaults to the Snapshot id."
9596
LogStart = "UNIX Epoch-formatted starting date and time for the range of log messages to retrieve. This value defaults to 24 hours prior to the current timestamp."
9697
LogEnd = "Ending date and time for the range of log messages to retrieve, given in UNIX time. Defaults to the start date plus 24 hours, if the start date is set. If start date is not provided, ending time defaults to the current time."
9798
MeasurementStart = "ISO 8601-formatted date and time that specifies when to start retrieving measurements. You can't set this parameter and period in the same request."

0 commit comments

Comments
 (0)