|
4 | 4 | "context"
|
5 | 5 | "errors"
|
6 | 6 | "fmt"
|
| 7 | + "reflect" |
7 | 8 | "strings"
|
8 | 9 | "time"
|
9 | 10 |
|
@@ -211,9 +212,38 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di
|
211 | 212 | }
|
212 | 213 |
|
213 | 214 | func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
|
214 |
| - return diag.Errorf("Updating a global cluster configuration resource is not allowed as it would " + |
215 |
| - "leave the index and shard key on the related collection in an inconsistent state.\n" + |
216 |
| - "Please read our official documentation for more information.") |
| 215 | + connV2 := meta.(*config.MongoDBClient).AtlasV2 |
| 216 | + ids := conversion.DecodeStateID(d.Id()) |
| 217 | + projectID := ids["project_id"] |
| 218 | + clusterName := ids["cluster_name"] |
| 219 | + |
| 220 | + if d.HasChange("managed_namespaces") { |
| 221 | + oldMN, newMN := d.GetChange("managed_namespaces") |
| 222 | + oldList := oldMN.(*schema.Set).List() |
| 223 | + newList := newMN.(*schema.Set).List() |
| 224 | + if err := updateManagedNamespaces(ctx, connV2, projectID, clusterName, oldList, newList); err != nil { |
| 225 | + return diag.FromErr(fmt.Errorf(errorGlobalClusterUpdate, clusterName, err)) |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 229 | + if d.HasChange("custom_zone_mappings") { |
| 230 | + oldZN, newZN := d.GetChange("custom_zone_mappings") |
| 231 | + oldSet := oldZN.(*schema.Set) |
| 232 | + newSet := newZN.(*schema.Set) |
| 233 | + if err := updateCustomZoneMappings(ctx, connV2, projectID, clusterName, oldSet, newSet); err != nil { |
| 234 | + return diag.FromErr(fmt.Errorf(errorGlobalClusterUpdate, clusterName, err)) |
| 235 | + } |
| 236 | + } |
| 237 | + return resourceRead(ctx, d, meta) |
| 238 | +} |
| 239 | + |
| 240 | +// convertInterfaceSlice is a helper function that converts []map[string]any into []any |
| 241 | +func convertInterfaceSlice(input []map[string]any) []any { |
| 242 | + var out []any |
| 243 | + for _, v := range input { |
| 244 | + out = append(out, v) |
| 245 | + } |
| 246 | + return out |
217 | 247 | }
|
218 | 248 |
|
219 | 249 | func resourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
|
@@ -335,3 +365,103 @@ func newCustomZoneMappings(tfList []any) *[]admin.ZoneMapping {
|
335 | 365 |
|
336 | 366 | return &apiObjects
|
337 | 367 | }
|
| 368 | + |
| 369 | +func addManagedNamespaces(ctx context.Context, connV2 *admin.APIClient, add []any, projectID, clusterName string) error { |
| 370 | + for _, m := range add { |
| 371 | + mn := m.(map[string]any) |
| 372 | + |
| 373 | + addManagedNamespace := &admin.ManagedNamespaces{ |
| 374 | + Collection: mn["collection"].(string), |
| 375 | + Db: mn["db"].(string), |
| 376 | + CustomShardKey: mn["custom_shard_key"].(string), |
| 377 | + } |
| 378 | + if isCustomShardKeyHashed, okCustomShard := mn["is_custom_shard_key_hashed"]; okCustomShard { |
| 379 | + addManagedNamespace.IsCustomShardKeyHashed = conversion.Pointer[bool](isCustomShardKeyHashed.(bool)) |
| 380 | + } |
| 381 | + if isShardKeyUnique, okShard := mn["is_shard_key_unique"]; okShard { |
| 382 | + addManagedNamespace.IsShardKeyUnique = conversion.Pointer[bool](isShardKeyUnique.(bool)) |
| 383 | + } |
| 384 | + _, _, err := connV2.GlobalClustersApi.CreateManagedNamespace(ctx, projectID, clusterName, addManagedNamespace).Execute() |
| 385 | + if err != nil { |
| 386 | + return err |
| 387 | + } |
| 388 | + } |
| 389 | + return nil |
| 390 | +} |
| 391 | + |
| 392 | +// buildManagedNamespacesMap converts a list of managed_namespace entries into a map keyed by "collection:db" |
| 393 | +func buildManagedNamespacesMap(list []any) map[string]map[string]any { |
| 394 | + namespacesMap := make(map[string]map[string]any) |
| 395 | + for _, item := range list { |
| 396 | + m := item.(map[string]any) |
| 397 | + key := fmt.Sprintf("%s:%s", m["collection"].(string), m["db"].(string)) |
| 398 | + namespacesMap[key] = m |
| 399 | + } |
| 400 | + return namespacesMap |
| 401 | +} |
| 402 | + |
| 403 | +// diffManagedNamespaces calculates the difference between old and new managed_namespaces. |
| 404 | +// Returns slices of namespaces to add and remove; errors out on modifications. |
| 405 | +func diffManagedNamespaces(oldList, newList []any) (toAdd, toRemove []map[string]any, err error) { |
| 406 | + oldMap := buildManagedNamespacesMap(oldList) |
| 407 | + newMap := buildManagedNamespacesMap(newList) |
| 408 | + for key, oldEntry := range oldMap { |
| 409 | + if newEntry, exists := newMap[key]; exists { |
| 410 | + // Modification is not allowed. |
| 411 | + if !reflect.DeepEqual(oldEntry, newEntry) { |
| 412 | + return nil, nil, fmt.Errorf("managed namespace for collection '%s' in db '%s' cannot be modified", oldEntry["collection"], oldEntry["db"]) |
| 413 | + } |
| 414 | + } else { |
| 415 | + toRemove = append(toRemove, oldEntry) |
| 416 | + } |
| 417 | + } |
| 418 | + for key, newEntry := range newMap { |
| 419 | + if _, exists := oldMap[key]; !exists { |
| 420 | + toAdd = append(toAdd, newEntry) |
| 421 | + } |
| 422 | + } |
| 423 | + return toAdd, toRemove, nil |
| 424 | +} |
| 425 | + |
| 426 | +// updateManagedNamespaces encapsulates diffing and applying removals/additions. |
| 427 | +func updateManagedNamespaces(ctx context.Context, connV2 *admin.APIClient, projectID, clusterName string, oldList, newList []any) error { |
| 428 | + toAdd, toRemove, err := diffManagedNamespaces(oldList, newList) |
| 429 | + if err != nil { |
| 430 | + return err |
| 431 | + } |
| 432 | + if len(toRemove) > 0 { |
| 433 | + if err := removeManagedNamespaces(ctx, connV2, convertInterfaceSlice(toRemove), projectID, clusterName); err != nil { |
| 434 | + return err |
| 435 | + } |
| 436 | + } |
| 437 | + if len(toAdd) > 0 { |
| 438 | + if err := addManagedNamespaces(ctx, connV2, convertInterfaceSlice(toAdd), projectID, clusterName); err != nil { |
| 439 | + return err |
| 440 | + } |
| 441 | + } |
| 442 | + return nil |
| 443 | +} |
| 444 | + |
| 445 | +// updateCustomZoneMappings encapsulates diffing and applying changes for custom_zone_mappings. |
| 446 | +func updateCustomZoneMappings(ctx context.Context, connV2 *admin.APIClient, projectID, clusterName string, oldSet, newSet *schema.Set) error { |
| 447 | + removed := oldSet.Difference(newSet).List() |
| 448 | + added := newSet.Difference(oldSet).List() |
| 449 | + |
| 450 | + if len(removed) > 0 { |
| 451 | + // Allow deletion only if all mappings are removed. |
| 452 | + if newSet.Len() != 0 { |
| 453 | + return fmt.Errorf("partial deletion of custom_zone_mappings is not allowed; remove either all mappings or none") |
| 454 | + } |
| 455 | + if _, _, err := connV2.GlobalClustersApi.DeleteAllCustomZoneMappings(ctx, projectID, clusterName).Execute(); err != nil { |
| 456 | + return err |
| 457 | + } |
| 458 | + } |
| 459 | + if len(added) > 0 { |
| 460 | + if _, _, err := connV2.GlobalClustersApi.CreateCustomZoneMapping(ctx, projectID, clusterName, &admin.CustomZoneMappings{ |
| 461 | + CustomZoneMappings: newCustomZoneMappings(added), |
| 462 | + }).Execute(); err != nil { |
| 463 | + return err |
| 464 | + } |
| 465 | + } |
| 466 | + return nil |
| 467 | +} |
0 commit comments