1// Copyright 2021 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 15package mongodbatlas 16 17import ( 18 "context" 19 "fmt" 20 "net/http" 21 "net/url" 22) 23 24const clustersPath = "groups/%s/clusters" 25 26// ClustersService is an interface for interfacing with the Clusters 27// endpoints of the MongoDB Atlas API. 28// See more: https://docs.atlas.mongodb.com/reference/api/clusters/ 29type ClustersService interface { 30 List(context.Context, string, *ListOptions) ([]Cluster, *Response, error) 31 Get(context.Context, string, string) (*Cluster, *Response, error) 32 Create(context.Context, string, *Cluster) (*Cluster, *Response, error) 33 Update(context.Context, string, string, *Cluster) (*Cluster, *Response, error) 34 Delete(context.Context, string, string) (*Response, error) 35 UpdateProcessArgs(context.Context, string, string, *ProcessArgs) (*ProcessArgs, *Response, error) 36 GetProcessArgs(context.Context, string, string) (*ProcessArgs, *Response, error) 37} 38 39// ClustersServiceOp handles communication with the Cluster related methods 40// of the MongoDB Atlas API 41type ClustersServiceOp service 42 43var _ ClustersService = &ClustersServiceOp{} 44 45// AutoScaling configures your cluster to automatically scale its storage 46type AutoScaling struct { 47 AutoIndexingEnabled *bool `json:"autoIndexingEnabled,omitempty"` // Autopilot mode is only available if you are enrolled in the Auto Pilot Early Access program. 48 Compute *Compute `json:"compute,omitempty"` 49 DiskGBEnabled *bool `json:"diskGBEnabled,omitempty"` 50} 51 52// Compute Specifies whether the cluster automatically scales its cluster tier and whether the cluster can scale down. 53type Compute struct { 54 Enabled *bool `json:"enabled,omitempty"` 55 ScaleDownEnabled *bool `json:"scaleDownEnabled,omitempty"` 56 MinInstanceSize string `json:"minInstanceSize,omitempty"` 57 MaxInstanceSize string `json:"maxInstanceSize,omitempty"` 58} 59 60// BiConnector specifies BI Connector for Atlas configuration on this cluster 61type BiConnector struct { 62 Enabled *bool `json:"enabled,omitempty"` 63 ReadPreference string `json:"readPreference,omitempty"` 64} 65 66// ProviderSettings configuration for the provisioned servers on which MongoDB runs. The available options are specific to the cloud service provider. 67type ProviderSettings struct { 68 BackingProviderName string `json:"backingProviderName,omitempty"` 69 DiskIOPS *int64 `json:"diskIOPS,omitempty"` 70 DiskTypeName string `json:"diskTypeName,omitempty"` 71 EncryptEBSVolume *bool `json:"encryptEBSVolume,omitempty"` 72 InstanceSizeName string `json:"instanceSizeName,omitempty"` 73 ProviderName string `json:"providerName,omitempty"` 74 RegionName string `json:"regionName,omitempty"` 75 VolumeType string `json:"volumeType,omitempty"` 76 AutoScaling *AutoScaling `json:"autoScaling,omitempty"` 77} 78 79// RegionsConfig describes the region’s priority in elections and the number and type of MongoDB nodes Atlas deploys to the region. 80type RegionsConfig struct { 81 AnalyticsNodes *int64 `json:"analyticsNodes,omitempty"` 82 ElectableNodes *int64 `json:"electableNodes,omitempty"` 83 Priority *int64 `json:"priority,omitempty"` 84 ReadOnlyNodes *int64 `json:"readOnlyNodes,omitempty"` 85} 86 87// ReplicationSpec represents a configuration for cluster regions 88type ReplicationSpec struct { 89 ID string `json:"id,omitempty"` 90 NumShards *int64 `json:"numShards,omitempty"` 91 ZoneName string `json:"zoneName,omitempty"` 92 RegionsConfig map[string]RegionsConfig `json:"regionsConfig,omitempty"` 93} 94 95// PrivateEndpoint connection strings. Each object describes the connection strings 96// you can use to connect to this cluster through a private endpoint. 97// Atlas returns this parameter only if you deployed a private endpoint to all regions 98// to which you deployed this cluster's nodes. 99type PrivateEndpoint struct { 100 ConnectionString string `json:"connectionString,omitempty"` 101 Endpoints []Endpoint `json:"endpoints,omitempty"` 102 SRVConnectionString string `json:"srvConnectionString,omitempty"` 103 Type string `json:"type,omitempty"` 104} 105 106// Endpoint through which you connect to Atlas 107type Endpoint struct { 108 EndpointID string `json:"endpointId,omitempty"` 109 ProviderName string `json:"providerName,omitempty"` 110 Region string `json:"region,omitempty"` 111} 112 113// ConnectionStrings configuration for applications use to connect to this cluster 114type ConnectionStrings struct { 115 Standard string `json:"standard,omitempty"` 116 StandardSrv string `json:"standardSrv,omitempty"` 117 PrivateEndpoint []PrivateEndpoint `json:"privateEndpoint,omitempty"` 118 AwsPrivateLink map[string]string `json:"awsPrivateLink,omitempty"` // Deprecated: Use connectionStrings.PrivateEndpoint[n].ConnectionString 119 AwsPrivateLinkSrv map[string]string `json:"awsPrivateLinkSrv,omitempty"` // Deprecated: Use ConnectionStrings.privateEndpoint[n].SRVConnectionString 120 Private string `json:"private,omitempty"` 121 PrivateSrv string `json:"privateSrv,omitempty"` 122} 123 124// Cluster represents MongoDB cluster. 125type Cluster struct { 126 AutoScaling *AutoScaling `json:"autoScaling,omitempty"` 127 BackupEnabled *bool `json:"backupEnabled,omitempty"` // Deprecated: Use ProviderBackupEnabled instead 128 BiConnector *BiConnector `json:"biConnector,omitempty"` 129 ClusterType string `json:"clusterType,omitempty"` 130 DiskSizeGB *float64 `json:"diskSizeGB,omitempty"` 131 EncryptionAtRestProvider string `json:"encryptionAtRestProvider,omitempty"` 132 Labels []Label `json:"labels,omitempty"` 133 ID string `json:"id,omitempty"` 134 GroupID string `json:"groupId,omitempty"` 135 MongoDBVersion string `json:"mongoDBVersion,omitempty"` 136 MongoDBMajorVersion string `json:"mongoDBMajorVersion,omitempty"` 137 MongoURI string `json:"mongoURI,omitempty"` 138 MongoURIUpdated string `json:"mongoURIUpdated,omitempty"` 139 MongoURIWithOptions string `json:"mongoURIWithOptions,omitempty"` 140 Name string `json:"name,omitempty"` 141 NumShards *int64 `json:"numShards,omitempty"` 142 Paused *bool `json:"paused,omitempty"` 143 PitEnabled *bool `json:"pitEnabled,omitempty"` 144 ProviderBackupEnabled *bool `json:"providerBackupEnabled,omitempty"` 145 ProviderSettings *ProviderSettings `json:"providerSettings,omitempty"` 146 ReplicationFactor *int64 `json:"replicationFactor,omitempty"` 147 ReplicationSpec map[string]RegionsConfig `json:"replicationSpec,omitempty"` 148 ReplicationSpecs []ReplicationSpec `json:"replicationSpecs,omitempty"` 149 SrvAddress string `json:"srvAddress,omitempty"` 150 StateName string `json:"stateName,omitempty"` 151 ConnectionStrings *ConnectionStrings `json:"connectionStrings,omitempty"` 152} 153 154// ProcessArgs represents the advanced configuration options for the cluster 155type ProcessArgs struct { 156 FailIndexKeyTooLong *bool `json:"failIndexKeyTooLong,omitempty"` 157 JavascriptEnabled *bool `json:"javascriptEnabled,omitempty"` 158 MinimumEnabledTLSProtocol string `json:"minimumEnabledTlsProtocol,omitempty"` 159 NoTableScan *bool `json:"noTableScan,omitempty"` 160 OplogSizeMB *int64 `json:"oplogSizeMB,omitempty"` 161 SampleSizeBIConnector *int64 `json:"sampleSizeBIConnector,omitempty"` 162 SampleRefreshIntervalBIConnector *int64 `json:"sampleRefreshIntervalBIConnector,omitempty"` 163} 164 165// clustersResponse is the response from the ClustersService.List. 166type clustersResponse struct { 167 Links []*Link `json:"links,omitempty"` 168 Results []Cluster `json:"results,omitempty"` 169 TotalCount int `json:"totalCount,omitempty"` 170} 171 172// DefaultDiskSizeGB represents the Tier and the default disk size for each one 173// it can be use like: DefaultDiskSizeGB["AWS"]["M10"] 174var DefaultDiskSizeGB = map[string]map[string]float64{ 175 "TENANT": { 176 "M2": 2, 177 "M5": 5, 178 }, 179 "AWS": { 180 "M10": 10, 181 "M20": 20, 182 "M30": 40, 183 "M40": 80, 184 "R40": 80, 185 "M40_NVME": 380, 186 "M50": 160, 187 "R50": 160, 188 "M50_NVME": 760, 189 "M60": 320, 190 "R60": 320, 191 "M60_NVME": 1600, 192 "M80": 750, 193 "R80": 750, 194 "M80_NVME": 1600, 195 "M140": 1000, 196 "M200": 1500, 197 "R200": 1500, 198 "M200_NVME": 3100, 199 "M300": 2000, 200 "R300": 2000, 201 "R400": 3000, 202 "M400_NVME": 4000, 203 }, 204 "GCP": { 205 "M10": 10, 206 "M20": 20, 207 "M30": 40, 208 "M40": 80, 209 "M50": 160, 210 "M60": 320, 211 "M80": 750, 212 "M200": 1500, 213 "M300": 2200, 214 }, 215 "AZURE": { 216 "M10": 32, 217 "M20": 32, 218 "M30": 32, 219 "M40": 128, 220 "M50": 128, 221 "M60": 128, 222 "M80": 256, 223 "M200": 256, 224 }, 225} 226 227// List all clusters in the project associated to {GROUP-ID}. 228// See more: https://docs.atlas.mongodb.com/reference/api/clusters-get-all/ 229func (s *ClustersServiceOp) List(ctx context.Context, groupID string, listOptions *ListOptions) ([]Cluster, *Response, error) { 230 path := fmt.Sprintf(clustersPath, groupID) 231 232 // Add query params from listOptions 233 path, err := setListOptions(path, listOptions) 234 if err != nil { 235 return nil, nil, err 236 } 237 238 req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil) 239 if err != nil { 240 return nil, nil, err 241 } 242 243 root := new(clustersResponse) 244 resp, err := s.Client.Do(ctx, req, root) 245 if err != nil { 246 return nil, resp, err 247 } 248 249 if l := root.Links; l != nil { 250 resp.Links = l 251 } 252 253 return root.Results, resp, nil 254} 255 256// Get gets the cluster specified to {ClUSTER-NAME} from the project associated to {GROUP-ID}. 257// See more: https://docs.atlas.mongodb.com/reference/api/clusters-get-one/ 258func (s *ClustersServiceOp) Get(ctx context.Context, groupID, clusterName string) (*Cluster, *Response, error) { 259 if err := checkClusterNameParam(clusterName); err != nil { 260 return nil, nil, err 261 } 262 263 basePath := fmt.Sprintf(clustersPath, groupID) 264 escapedEntry := url.PathEscape(clusterName) 265 path := fmt.Sprintf("%s/%s", basePath, escapedEntry) 266 267 req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil) 268 if err != nil { 269 return nil, nil, err 270 } 271 272 root := new(Cluster) 273 resp, err := s.Client.Do(ctx, req, root) 274 if err != nil { 275 return nil, resp, err 276 } 277 278 return root, resp, err 279} 280 281// Create adds a cluster to the project associated to {GROUP-ID}. 282// See more: https://docs.atlas.mongodb.com/reference/api/clusters-create-one/ 283func (s *ClustersServiceOp) Create(ctx context.Context, groupID string, createRequest *Cluster) (*Cluster, *Response, error) { 284 if createRequest == nil { 285 return nil, nil, NewArgError("createRequest", "cannot be nil") 286 } 287 288 path := fmt.Sprintf(clustersPath, groupID) 289 290 req, err := s.Client.NewRequest(ctx, http.MethodPost, path, createRequest) 291 if err != nil { 292 return nil, nil, err 293 } 294 295 root := new(Cluster) 296 resp, err := s.Client.Do(ctx, req, root) 297 if err != nil { 298 return nil, resp, err 299 } 300 301 return root, resp, err 302} 303 304// Update a cluster in the project associated to {GROUP-ID} 305// See more: https://docs.atlas.mongodb.com/reference/api/clusters-modify-one/ 306func (s *ClustersServiceOp) Update(ctx context.Context, groupID, clusterName string, updateRequest *Cluster) (*Cluster, *Response, error) { 307 if updateRequest == nil { 308 return nil, nil, NewArgError("updateRequest", "cannot be nil") 309 } 310 311 basePath := fmt.Sprintf(clustersPath, groupID) 312 path := fmt.Sprintf("%s/%s", basePath, clusterName) 313 314 req, err := s.Client.NewRequest(ctx, http.MethodPatch, path, updateRequest) 315 if err != nil { 316 return nil, nil, err 317 } 318 319 root := new(Cluster) 320 resp, err := s.Client.Do(ctx, req, root) 321 if err != nil { 322 return nil, resp, err 323 } 324 325 return root, resp, err 326} 327 328// Delete the cluster specified to {CLUSTER-NAME} from the project associated to {GROUP-ID}. 329// See more: https://docs.atlas.mongodb.com/reference/api/clusters-delete-one/ 330func (s *ClustersServiceOp) Delete(ctx context.Context, groupID, clusterName string) (*Response, error) { 331 if clusterName == "" { 332 return nil, NewArgError("clusterName", "must be set") 333 } 334 335 basePath := fmt.Sprintf(clustersPath, groupID) 336 escapedEntry := url.PathEscape(clusterName) 337 path := fmt.Sprintf("%s/%s", basePath, escapedEntry) 338 339 req, err := s.Client.NewRequest(ctx, http.MethodDelete, path, nil) 340 if err != nil { 341 return nil, err 342 } 343 344 resp, err := s.Client.Do(ctx, req, nil) 345 346 return resp, err 347} 348 349// UpdateProcessArgs Modifies Advanced Configuration Options for One Cluster 350// See more: https://docs.atlas.mongodb.com/reference/api/clusters-modify-advanced-configuration-options/ 351func (s *ClustersServiceOp) UpdateProcessArgs(ctx context.Context, groupID, clusterName string, updateRequest *ProcessArgs) (*ProcessArgs, *Response, error) { 352 if updateRequest == nil { 353 return nil, nil, NewArgError("updateRequest", "cannot be nil") 354 } 355 356 basePath := fmt.Sprintf(clustersPath, groupID) 357 path := fmt.Sprintf("%s/%s/processArgs", basePath, clusterName) 358 359 req, err := s.Client.NewRequest(ctx, http.MethodPatch, path, updateRequest) 360 if err != nil { 361 return nil, nil, err 362 } 363 364 root := new(ProcessArgs) 365 resp, err := s.Client.Do(ctx, req, root) 366 if err != nil { 367 return nil, resp, err 368 } 369 370 return root, resp, err 371} 372 373// GetProcessArgs gets the Advanced Configuration Options for One Cluster 374// See more: https://docs.atlas.mongodb.com/reference/api/clusters-get-advanced-configuration-options/#get-advanced-configuration-options-for-one-cluster 375func (s *ClustersServiceOp) GetProcessArgs(ctx context.Context, groupID, clusterName string) (*ProcessArgs, *Response, error) { 376 if err := checkClusterNameParam(clusterName); err != nil { 377 return nil, nil, err 378 } 379 380 basePath := fmt.Sprintf(clustersPath, groupID) 381 escapedEntry := url.PathEscape(clusterName) 382 path := fmt.Sprintf("%s/%s/processArgs", basePath, escapedEntry) 383 384 req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil) 385 if err != nil { 386 return nil, nil, err 387 } 388 389 root := new(ProcessArgs) 390 resp, err := s.Client.Do(ctx, req, root) 391 if err != nil { 392 return nil, resp, err 393 } 394 395 return root, resp, err 396} 397 398func checkClusterNameParam(clusterName string) error { 399 if clusterName == "" { 400 return NewArgError("name", "must be set") 401 } 402 return nil 403} 404