Skip to content

CLOUDP-164347: Add --flag file to the atlas cluster index create command #2768

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

Merged
merged 12 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions docs/command/atlas-clusters-indexes-create.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,32 @@ Options
- Name of the cluster.
* - --collection
- string
- true
- false
- Name of the collection.

Mutually exclusive with --file.
* - --db
- string
- true
- false
- Name of the database.

Mutually exclusive with --file.
* - -f, --file
- string
- false
- Path to an optional JSON configuration file that defines index settings.

Mutually exclusive with --db, --collection, --key.
* - -h, --help
-
- false
- help for create
* - --key
- strings
- true
- false
- Field to be indexed and the type of index in the following format: field:type.

Mutually exclusive with --file.
* - --projectId
- string
- false
Expand Down Expand Up @@ -112,3 +124,9 @@ Examples
# Create a compound index named property_room_bedrooms on the
listings collection of the realestate database:
atlas clusters indexes create property_room_bedrooms --clusterName Cluster0 --collection listings --db realestate --key property_type:1 --key room_type:1 --key bedrooms:1


.. code-block::

# Create an index named my_index from a JSON configuration file named myfile.json:
atlas clusters indexes create my_index --clusterName Cluster0 --file file.json
64 changes: 56 additions & 8 deletions internal/cli/atlas/clusters/indexes/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import (
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/file"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
"github.com/spf13/afero"
"github.com/spf13/cobra"
atlasv2 "go.mongodb.org/atlas-sdk/v20231115007/admin"
)
Expand All @@ -35,9 +37,11 @@ type CreateOpts struct {
name string
db string
collection string
filename string
keys []string
sparse bool
store store.IndexCreator
fs afero.Fs
}

func (opts *CreateOpts) initStore(ctx context.Context) func() error {
Expand All @@ -61,11 +65,43 @@ func (opts *CreateOpts) Run() error {
}

func (opts *CreateOpts) newIndex() (*atlasv2.DatabaseRollingIndexRequest, error) {
if opts.filename != "" {
return opts.newIndexViaFile()
}

return opts.newIndexViaFlags()
}

func (opts *CreateOpts) newIndexViaFile() (*atlasv2.DatabaseRollingIndexRequest, error) {
i := new(atlasv2.DatabaseRollingIndexRequest)
if err := file.Load(opts.fs, opts.filename, i); err != nil {
return nil, err
}

if opts.name == "" {
return i, nil
}

if i.Options == nil {
i.Options = &atlasv2.IndexOptions{
Name: &opts.name,
}

return i, nil
}

i.Options.Name = &opts.name

return i, nil
}

func (opts *CreateOpts) newIndexViaFlags() (*atlasv2.DatabaseRollingIndexRequest, error) {
i := new(atlasv2.DatabaseRollingIndexRequest)
keys, err := opts.indexKeys()
if err != nil {
return nil, err
}
i := new(atlasv2.DatabaseRollingIndexRequest)

i.Db = opts.db
i.Collection = opts.collection
i.Keys = &keys
Expand Down Expand Up @@ -100,9 +136,11 @@ func (opts *CreateOpts) indexKeys() ([]map[string]string, error) {
}

// CreateBuilder builds a cobra.Command that can run as:
// mcli atlas clusters index create [indexName] --clusterName clusterName --collection collection --dbName dbName [--key field:type].
// mcli atlas clusters index create [indexName] --clusterName clusterName --collection collection --dbName dbName [--key field:type] --file filename.
func CreateBuilder() *cobra.Command {
opts := &CreateOpts{}
opts := &CreateOpts{
fs: afero.NewOsFs(),
}
cmd := &cobra.Command{
Use: "create [indexName]",
Short: "Create a rolling index for the specified cluster for your project.",
Expand All @@ -116,8 +154,17 @@ func CreateBuilder() *cobra.Command {

# Create a compound index named property_room_bedrooms on the
listings collection of the realestate database:
atlas clusters indexes create property_room_bedrooms --clusterName Cluster0 --collection listings --db realestate --key property_type:1 --key room_type:1 --key bedrooms:1`,
atlas clusters indexes create property_room_bedrooms --clusterName Cluster0 --collection listings --db realestate --key property_type:1 --key room_type:1 --key bedrooms:1

# Create an index named my_index from a JSON configuration file named myfile.json:
atlas clusters indexes create my_index --clusterName Cluster0 --file file.json`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
if opts.filename == "" {
_ = cmd.MarkFlagRequired(flag.Database)
_ = cmd.MarkFlagRequired(flag.Collection)
_ = cmd.MarkFlagRequired(flag.Key)
}

return opts.PreRunE(opts.ValidateProjectID, opts.initStore(cmd.Context()))
},
RunE: func(_ *cobra.Command, args []string) error {
Expand All @@ -133,13 +180,14 @@ func CreateBuilder() *cobra.Command {
cmd.Flags().StringVar(&opts.collection, flag.Collection, "", usage.Collection)
cmd.Flags().StringSliceVar(&opts.keys, flag.Key, []string{}, usage.Key)
cmd.Flags().BoolVar(&opts.sparse, flag.Sparse, false, usage.Sparse)
cmd.Flags().StringVarP(&opts.filename, flag.File, flag.FileShort, "", usage.IndexFilename)

cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID)

_ = cmd.MarkFlagRequired(flag.ClusterName)
_ = cmd.MarkFlagRequired(flag.Database)
_ = cmd.MarkFlagRequired(flag.Collection)
_ = cmd.MarkFlagRequired(flag.Key)
cmd.MarkFlagsMutuallyExclusive(flag.File, flag.Database)
cmd.MarkFlagsMutuallyExclusive(flag.File, flag.Collection)
cmd.MarkFlagsMutuallyExclusive(flag.File, flag.Key)

_ = cmd.MarkFlagRequired(flag.ClusterName)
return cmd
}
50 changes: 49 additions & 1 deletion internal/cli/atlas/clusters/indexes/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/test"
"github.com/spf13/afero"
)

func TestCreate_Run(t *testing.T) {
Expand Down Expand Up @@ -50,11 +51,58 @@ func TestCreate_Run(t *testing.T) {
}
}

func TestCreateWithFile_Run(t *testing.T) {
ctrl := gomock.NewController(t)
mockStore := mocks.NewMockIndexCreator(ctrl)
appFS := afero.NewMemMapFs()
fileJSON := `
{
"collection": "collectionName",
"db": "dbName",
"options":{
"sparse": true,
"unique": true,
"textIndexVersion": 1,
"name": "myIndex",
"min": 1,
"max": 10,
"language_override": "test",
"hidden": true,
"expireAfterSeconds": 2,
"default_language": "test",
"default_language": "test",
"columnstoreProjection": {"key":1, "key2":2},
"bucketSize": 2,
"bits": 222,
"background": false,
"2dsphereIndexVersion": 2
}
}`
fileName := "atlas_cluster_index_create_test.json"
_ = afero.WriteFile(appFS, fileName, []byte(fileJSON), 0600)
createOpts := &CreateOpts{
filename: fileName,
store: mockStore,
fs: appFS,
}

index, _ := createOpts.newIndex()
mockStore.
EXPECT().
CreateIndex(createOpts.ProjectID, createOpts.clusterName, index).
Return(nil).
Times(1)

if err := createOpts.Run(); err != nil {
t.Fatalf("Run() unexpected error: %v", err)
}
}

func TestCreateBuilder(t *testing.T) {
test.CmdValidator(
t,
CreateBuilder(),
0,
[]string{flag.ClusterName, flag.Database, flag.Collection, flag.Key, flag.Sparse, flag.ProjectID},
[]string{flag.ClusterName, flag.Database, flag.Collection, flag.Key, flag.Sparse, flag.ProjectID, flag.File},
)
}
1 change: 1 addition & 0 deletions internal/usage/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ dbName and collection are required only for built-in roles.`
MaxDate = "Maximum created date. This option returns events whose created date is less than or equal to the specified value."
MinDate = "Minimum created date. This option returns events whose created date is greater than or equal to the specified value."
ClusterFilename = "Path to an optional JSON configuration file that defines cluster settings. To learn more about cluster configuration files for the Atlas CLI, see https://dochub.mongodb.org/core/cluster-config-file-atlascli. To learn more about cluster configuration files for MongoCLI, see https://dochub.mongodb.org/core/mms-cluster-settings-file-mcli."
IndexFilename = "Path to an optional JSON configuration file that defines index settings."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you intend to have documentation for the file structure? if so make sure to ask for a dochub link and add it here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need the doc team to create the doc page and then we can update the description here. Let me know if I am missing something

Copy link
Collaborator

@fmenezes fmenezes Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can have the link ahead of time if you ask them and once the page is up they redirect it. we've done this way before

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can alternatively open a docsp ticket and ask them to update this line, make sure to add a link to where they should add the link if that is the approach you prefer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we cannot use the example in the open api spec (it does not work) and I haven't found any examples in the doc. The doc team will need to use the files in the data folders included in this PR 😭

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sarahsimpers FYI as I think you've done some work here recently on the config files for the CLI.

BackupFilename = "Path to an optional JSON configuration file that defines backup schedule settings. To learn about the cloud backup configuration file for the Atlas CLI, see https://dochub.mongodb.org/core/cloud-backup-config-file."
SearchFilename = "Name of the JSON index configuration file to use. To learn about the Atlas Search index configuration file, see https://dochub.mongodb.org/core/search-index-config-file-atlascli. To learn about the Atlas Search index syntax and options that you can define in your configuration file, see https://dochub.mongodb.org/core/index-definitions-fts."
SearchNodesFilename = "Name of the JSON index configuration file to use."
Expand Down
50 changes: 46 additions & 4 deletions test/e2e/atlas/clusters_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,54 @@ func TestClustersFile(t *testing.T) {
assert.Contains(t, string(resp), "Cluster available")
})

t.Run("Create Partial Index", func(t *testing.T) {
cmd := exec.Command(cliPath,
clustersEntity,
"indexes",
"create",
"--clusterName", clusterFileName,
"--file=data/create_partial_index.json",
"--projectId", g.projectID,
)
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))
})

t.Run("Create Sparse Index", func(t *testing.T) {
cmd := exec.Command(cliPath,
clustersEntity,
"indexes",
"create",
"--clusterName", clusterFileName,
"--file=data/create_sparse_index.json",
"--projectId", g.projectID,
)
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))
})

t.Run("Create 2dspere Index", func(t *testing.T) {
cmd := exec.Command(cliPath,
clustersEntity,
"indexes",
"create",
"--clusterName", clusterFileName,
"--file=data/create_2dspere_index.json",
"--projectId", g.projectID,
)
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))
})

t.Run("Update via file", func(t *testing.T) {
cmd := exec.Command(cliPath,
clustersEntity,
"update",
clusterFileName,
"--file=update_cluster_test.json",
"--file=data/update_cluster_test.json",
"--projectId", g.projectID,
"-o=json")

Expand Down Expand Up @@ -141,9 +183,9 @@ func generateClusterFile(mdbVersion string) (string, error) {
MongoDBMajorVersion: mdbVersion,
}

templateFile := "create_cluster_test.json"
templateFile := "data/create_cluster_test.json"
if service := os.Getenv("MCLI_SERVICE"); service == config.CloudGovService {
templateFile = "create_cluster_gov_test.json"
templateFile = "data/create_cluster_gov_test.json"
}

tmpl, err := template.ParseFiles(templateFile)
Expand All @@ -156,7 +198,7 @@ func generateClusterFile(mdbVersion string) (string, error) {
return "", err
}

const clusterFile = "create_cluster.json"
const clusterFile = "data/create_cluster.json"
file, err := os.Create(clusterFile)
if err != nil {
return "", err
Expand Down
24 changes: 24 additions & 0 deletions test/e2e/atlas/data/create_2dspere_index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"collation": {
"alternate": "non-ignorable",
"backwards": false,
"caseFirst": "lower",
"caseLevel": false,
"locale": "af",
"maxVariable": "punct",
"normalization": false,
"numericOrdering": false,
"strength": 3
},
"collection": "accounts",
"db": "sample_airbnb",
"keys": [
{
"test_field": "2dsphere"
}
],
"options": {
"name": "2dspereIndexTest",
"2dsphereIndexVersion": 2
}
}
27 changes: 27 additions & 0 deletions test/e2e/atlas/data/create_partial_index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"collation": {
"alternate": "non-ignorable",
"backwards": false,
"caseFirst": "lower",
"caseLevel": false,
"locale": "af",
"maxVariable": "punct",
"normalization": false,
"numericOrdering": false,
"strength": 3
},
"collection": "accounts",
"db": "sample_airbnb",
"keys": [
{
"property_type": "1",
"room_type": "1"
}
],
"options": {
"name": "PartialIndexTest",
"partialFilterExpression":{
"limit": {"$gt": 900}
}
}
}
24 changes: 24 additions & 0 deletions test/e2e/atlas/data/create_sparse_index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"collation": {
"alternate": "non-ignorable",
"backwards": false,
"caseFirst": "lower",
"caseLevel": false,
"locale": "af",
"maxVariable": "punct",
"normalization": false,
"numericOrdering": false,
"strength": 3
},
"collection": "accounts",
"db": "sample_airbnb",
"keys": [
{
"test_field": "1"
}
],
"options": {
"name": "SparseIndexTest",
"sparse": true
}
}
Loading