diff --git a/go.mod b/go.mod index bac3507934..0ba6bba70f 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,10 @@ go 1.12 require ( github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a + github.com/go-test/deep v1.0.1 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect github.com/hashicorp/terraform v0.12.1 - github.com/mongodb/go-client-mongodb-atlas v0.1.2 + github.com/mongodb/go-client-mongodb-atlas v0.1.3-0.20200110215223-d0530efb030d github.com/mwielbut/pointy v1.1.0 github.com/spf13/cast v1.3.0 ) - -replace github.com/mongodb/go-client-mongodb-atlas v0.1.2 => ../go-client-mongodb-atlas diff --git a/go.sum b/go.sum index ef7054bb72..2261e84ec4 100644 --- a/go.sum +++ b/go.sum @@ -265,10 +265,8 @@ github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51 h1:eD92Am0Qf3 github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mongodb/go-client-mongodb-atlas v0.1.2-0.20191219203423-452c38f9f27d h1:ujWegMeDbfZkPH0GxAC/X8UOM39VRMmxGehjOL+fSjQ= -github.com/mongodb/go-client-mongodb-atlas v0.1.2-0.20191219203423-452c38f9f27d/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= -github.com/mongodb/go-client-mongodb-atlas v0.1.2 h1:qmUme1TlQBPZupmXMnpD8DxnfGXLVGs3w+0Z17HBiSA= -github.com/mongodb/go-client-mongodb-atlas v0.1.2/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= +github.com/mongodb/go-client-mongodb-atlas v0.1.3-0.20200110215223-d0530efb030d h1:M+jxUj2Q20KeXstXElNlbpxg7QVx4ZR1o9vHDS3HEqo= +github.com/mongodb/go-client-mongodb-atlas v0.1.3-0.20200110215223-d0530efb030d/go.mod h1:LS8O0YLkA+sbtOb3fZLF10yY3tJM+1xATXMJ3oU35LU= github.com/mwielbut/pointy v1.1.0 h1:U5/YEfoIkaGCHv0St3CgjduqXID4FNRoyZgLM1kY9vg= github.com/mwielbut/pointy v1.1.0/go.mod h1:MvvO+uMFj9T5DMda33HlvogsFBX7pWWKAkFIn4teYwY= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= diff --git a/mongodbatlas/provider.go b/mongodbatlas/provider.go index 160044893b..968c791ef3 100644 --- a/mongodbatlas/provider.go +++ b/mongodbatlas/provider.go @@ -57,6 +57,7 @@ func Provider() terraform.ResourceProvider { "mongodbatlas_network_peering": resourceMongoDBAtlasNetworkPeering(), "mongodbatlas_encryption_at_rest": resourceMongoDBAtlasEncryptionAtRest(), "mongodbatlas_private_ip_mode": resourceMongoDBAtlasPrivateIPMode(), + "mongodbatlas_maintenance_window": resourceMongoDBAtlasMaintenanceWindow(), }, ConfigureFunc: providerConfigure, diff --git a/mongodbatlas/resource_mongodbatlas_maintenance_window.go b/mongodbatlas/resource_mongodbatlas_maintenance_window.go new file mode 100644 index 0000000000..8a0fa10a2b --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_maintenance_window.go @@ -0,0 +1,182 @@ +package mongodbatlas + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/mwielbut/pointy" + "github.com/spf13/cast" + + matlas "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas" +) + +const ( + errorMaintenanceCreate = "error creating the MongoDB Atlas Maintenance Window (%s): %s" + errorMaintenanceUpdate = "error updating the MongoDB Atlas Maintenance Window (%s): %s" + errorMaintenanceRead = "error reading the MongoDB Atlas Maintenance Window (%s): %s" + errorMaintenanceDelete = "error deleting the MongoDB Atlas Maintenance Window (%s): %s" + errorMaintenanceDefer = "error deferring the MongoDB Atlas Maintenance Window (%s): %s" +) + +func resourceMongoDBAtlasMaintenanceWindow() *schema.Resource { + return &schema.Resource{ + Create: resourceMongoDBAtlasMaintenanceWindowCreate, + Read: resourceMongoDBAtlasMaintenanceWindowRead, + Update: resourceMongoDBAtlasMaintenanceWindowUpdate, + Delete: resourceMongoDBAtlasMaintenanceWindowDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Required: true, + }, + "day_of_week": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(int) + if v < 1 || v > 7 { + errs = append(errs, fmt.Errorf("%q value should be between 1 and 7, got: %d", key, v)) + } + return + }, + }, + "hour_of_day": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"start_asap"}, + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + v := val.(int) + if v < 0 || v > 23 { + errs = append(errs, fmt.Errorf("%q value should be between 0 and 23, got: %d", key, v)) + } + return + }, + }, + "start_asap": { + Type: schema.TypeBool, + Computed: true, + }, + "number_of_deferrals": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "defer": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceMongoDBAtlasMaintenanceWindowCreate(d *schema.ResourceData, meta interface{}) error { + //Get the client connection. + conn := meta.(*matlas.Client) + + projectID := d.Get("project_id").(string) + + if deferValue := d.Get("defer").(bool); deferValue { + _, err := conn.MaintenanceWindows.Defer(context.Background(), projectID) + if err != nil { + return fmt.Errorf(errorMaintenanceDefer, projectID, err) + } + } + + maintenanceWindowReq := &matlas.MaintenanceWindow{} + if dayOfWeek, ok := d.GetOk("day_of_week"); ok { + maintenanceWindowReq.DayOfWeek = cast.ToInt(dayOfWeek) + } + if hourOfDay, ok := d.GetOkExists("hour_of_day"); ok { + maintenanceWindowReq.HourOfDay = pointy.Int(cast.ToInt(hourOfDay)) + } + if numberOfDeferrals, ok := d.GetOk("number_of_deferrals"); ok { + maintenanceWindowReq.NumberOfDeferrals = cast.ToInt(numberOfDeferrals) + } + + _, err := conn.MaintenanceWindows.Update(context.Background(), projectID, maintenanceWindowReq) + if err != nil { + return fmt.Errorf(errorMaintenanceCreate, projectID, err) + } + + d.SetId(projectID) + + return resourceMongoDBAtlasMaintenanceWindowRead(d, meta) +} + +func resourceMongoDBAtlasMaintenanceWindowRead(d *schema.ResourceData, meta interface{}) error { + //Get the client connection. + conn := meta.(*matlas.Client) + + maintenanceWindow, _, err := conn.MaintenanceWindows.Get(context.Background(), d.Id()) + if err != nil { + return fmt.Errorf(errorMaintenanceRead, d.Id(), err) + } + + if err := d.Set("day_of_week", maintenanceWindow.DayOfWeek); err != nil { + return fmt.Errorf(errorMaintenanceRead, d.Id(), err) + } + if err := d.Set("hour_of_day", maintenanceWindow.HourOfDay); err != nil { + return fmt.Errorf(errorMaintenanceRead, d.Id(), err) + } + if err := d.Set("number_of_deferrals", maintenanceWindow.NumberOfDeferrals); err != nil { + return fmt.Errorf(errorMaintenanceRead, d.Id(), err) + } + // start_asap is just display the state of the maintenance, + // and it doesn't able to set it because breacks the Terraform flow + // it can be used via API + if err := d.Set("start_asap", maintenanceWindow.StartASAP); err != nil { + return fmt.Errorf(errorMaintenanceRead, d.Id(), err) + } + + return nil +} + +func resourceMongoDBAtlasMaintenanceWindowUpdate(d *schema.ResourceData, meta interface{}) error { + //Get the client connection. + conn := meta.(*matlas.Client) + + maintenanceWindowReq := &matlas.MaintenanceWindow{} + + if d.HasChange("defer") { + _, err := conn.MaintenanceWindows.Defer(context.Background(), d.Id()) + if err != nil { + return fmt.Errorf(errorMaintenanceDefer, d.Id(), err) + } + } + + if d.HasChange("day_of_week") { + maintenanceWindowReq.DayOfWeek = cast.ToInt(d.Get("day_of_week")) + } + if d.HasChange("hour_of_day") { + maintenanceWindowReq.HourOfDay = pointy.Int(cast.ToInt(d.Get("hour_of_day"))) + } + if d.HasChange("number_of_deferrals") { + maintenanceWindowReq.NumberOfDeferrals = cast.ToInt(d.Get("number_of_deferrals")) + } + + _, err := conn.MaintenanceWindows.Update(context.Background(), d.Id(), maintenanceWindowReq) + if err != nil { + return fmt.Errorf(errorMaintenanceUpdate, d.Id(), err) + } + + return nil +} + +func resourceMongoDBAtlasMaintenanceWindowDelete(d *schema.ResourceData, meta interface{}) error { + //Get the client connection. + conn := meta.(*matlas.Client) + + _, err := conn.MaintenanceWindows.Reset(context.Background(), d.Id()) + if err != nil { + return fmt.Errorf(errorMaintenanceDelete, d.Id(), err) + } + + return nil +} diff --git a/mongodbatlas/resource_mongodbatlas_maintenance_window_test.go b/mongodbatlas/resource_mongodbatlas_maintenance_window_test.go new file mode 100644 index 0000000000..52ec67e641 --- /dev/null +++ b/mongodbatlas/resource_mongodbatlas_maintenance_window_test.go @@ -0,0 +1,172 @@ +package mongodbatlas + +import ( + "context" + "fmt" + "log" + "os" + "testing" + + "github.com/go-test/deep" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + matlas "github.com/mongodb/go-client-mongodb-atlas/mongodbatlas" + "github.com/spf13/cast" +) + +func TestAccResourceMongoDBAtlasMaintenanceWindow_basic(t *testing.T) { + var maintenance matlas.MaintenanceWindow + resourceName := "mongodbatlas_maintenance_window.test" + + projectID := os.Getenv("MONGODB_ATLAS_PROJECT_ID") + dayOfWeek := 7 + hourOfDay := 3 + + dayOfWeekUpdated := 4 + hourOfDayUpdated := 5 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMongoDBAtlasMaintenanceWindowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasMaintenanceWindowConfig(projectID, dayOfWeek, hourOfDay), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasMaintenanceWindowExists(resourceName, &maintenance), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "day_of_week"), + resource.TestCheckResourceAttrSet(resourceName, "hour_of_day"), + + resource.TestCheckResourceAttr(resourceName, "day_of_week", cast.ToString(dayOfWeek)), + resource.TestCheckResourceAttr(resourceName, "hour_of_day", cast.ToString(hourOfDay)), + resource.TestCheckResourceAttr(resourceName, "number_of_deferrals", "0"), + + testAccCheckMongoDBAtlasMaintenanceWindowAttributes("day_of_week", dayOfWeek, &maintenance.DayOfWeek), + ), + }, + { + Config: testAccMongoDBAtlasMaintenanceWindowConfig(projectID, dayOfWeekUpdated, hourOfDayUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasMaintenanceWindowExists(resourceName, &maintenance), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "day_of_week"), + resource.TestCheckResourceAttrSet(resourceName, "hour_of_day"), + + resource.TestCheckResourceAttr(resourceName, "day_of_week", cast.ToString(dayOfWeekUpdated)), + resource.TestCheckResourceAttr(resourceName, "hour_of_day", cast.ToString(hourOfDayUpdated)), + resource.TestCheckResourceAttr(resourceName, "number_of_deferrals", "0"), + + testAccCheckMongoDBAtlasMaintenanceWindowAttributes("day_of_week", dayOfWeekUpdated, &maintenance.DayOfWeek), + ), + }, + }, + }) +} + +func TestAccResourceMongoDBAtlasMaintenanceWindow_importBasic(t *testing.T) { + var maintenance matlas.MaintenanceWindow + resourceName := "mongodbatlas_maintenance_window.test" + + projectID := os.Getenv("MONGODB_ATLAS_PROJECT_ID") + dayOfWeek := 1 + hourOfDay := 3 + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckMongoDBAtlasMaintenanceWindowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMongoDBAtlasMaintenanceWindowConfig(projectID, dayOfWeek, hourOfDay), + Check: resource.ComposeTestCheckFunc( + testAccCheckMongoDBAtlasMaintenanceWindowExists(resourceName, &maintenance), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "day_of_week"), + resource.TestCheckResourceAttrSet(resourceName, "hour_of_day"), + + resource.TestCheckResourceAttr(resourceName, "day_of_week", cast.ToString(dayOfWeek)), + resource.TestCheckResourceAttr(resourceName, "hour_of_day", cast.ToString(hourOfDay)), + resource.TestCheckResourceAttr(resourceName, "number_of_deferrals", "0"), + resource.TestCheckResourceAttr(resourceName, "start_asap", "false"), + + testAccCheckMongoDBAtlasMaintenanceWindowAttributes("day_of_week", dayOfWeek, &maintenance.DayOfWeek), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccCheckMongoDBAtlasMaintenanceWindowImportStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project_id"}, + }, + }, + }) +} + +func testAccCheckMongoDBAtlasMaintenanceWindowExists(resourceName string, maintenance *matlas.MaintenanceWindow) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*matlas.Client) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + log.Printf("[DEBUG] projectID: %s", rs.Primary.ID) + + maintenanceWindow, _, err := conn.MaintenanceWindows.Get(context.Background(), rs.Primary.ID) + if err != nil { + return fmt.Errorf("Maintenance Window (%s) does not exist", rs.Primary.ID) + } + *maintenance = *maintenanceWindow + return nil + } +} + +func testAccCheckMongoDBAtlasMaintenanceWindowAttributes(attr string, expected int, got *int) resource.TestCheckFunc { + return func(s *terraform.State) error { + if diff := deep.Equal(expected, *got); diff != nil { + return fmt.Errorf("Bad %s \n got = %#v\nwant = %#v \ndiff = %#v", attr, expected, *got, diff) + } + return nil + } +} + +func testAccCheckMongoDBAtlasMaintenanceWindowDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*matlas.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "mongodbatlas_maintenance_window" { + continue + } + + _, _, err := conn.MaintenanceWindows.Get(context.Background(), rs.Primary.ID) + if err != nil { + return fmt.Errorf("Maintenance Window (%s) does not exist", rs.Primary.ID) + } + } + return nil +} + +func testAccCheckMongoDBAtlasMaintenanceWindowImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return rs.Primary.ID, nil + } +} + +func testAccMongoDBAtlasMaintenanceWindowConfig(projectID string, dayOfWeek, hourOfDay int) string { + return fmt.Sprintf(` + resource "mongodbatlas_maintenance_window" "test" { + project_id = "%s" + day_of_week = %d + hour_of_day = %d + }`, projectID, dayOfWeek, hourOfDay) +} diff --git a/vendor/github.com/go-test/deep/.gitignore b/vendor/github.com/go-test/deep/.gitignore new file mode 100644 index 0000000000..53f12f0f0e --- /dev/null +++ b/vendor/github.com/go-test/deep/.gitignore @@ -0,0 +1,2 @@ +*.swp +*.out diff --git a/vendor/github.com/go-test/deep/.travis.yml b/vendor/github.com/go-test/deep/.travis.yml new file mode 100644 index 0000000000..2279c61427 --- /dev/null +++ b/vendor/github.com/go-test/deep/.travis.yml @@ -0,0 +1,13 @@ +language: go + +go: + - 1.7 + - 1.8 + - 1.9 + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cover + +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/go-test/deep/CHANGES.md b/vendor/github.com/go-test/deep/CHANGES.md new file mode 100644 index 0000000000..4351819d68 --- /dev/null +++ b/vendor/github.com/go-test/deep/CHANGES.md @@ -0,0 +1,9 @@ +# go-test/deep Changelog + +## v1.0.1 released 2018-01-28 + +* Fixed #12: Arrays are not properly compared (samlitowitz) + +## v1.0.0 releaesd 2017-10-27 + +* First release diff --git a/vendor/github.com/go-test/deep/LICENSE b/vendor/github.com/go-test/deep/LICENSE new file mode 100644 index 0000000000..228ef16f74 --- /dev/null +++ b/vendor/github.com/go-test/deep/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright 2015-2017 Daniel Nichter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-test/deep/README.md b/vendor/github.com/go-test/deep/README.md new file mode 100644 index 0000000000..3b78eac7c1 --- /dev/null +++ b/vendor/github.com/go-test/deep/README.md @@ -0,0 +1,51 @@ +# Deep Variable Equality for Humans + +[![Go Report Card](https://goreportcard.com/badge/github.com/go-test/deep)](https://goreportcard.com/report/github.com/go-test/deep) [![Build Status](https://travis-ci.org/go-test/deep.svg?branch=master)](https://travis-ci.org/go-test/deep) [![Coverage Status](https://coveralls.io/repos/github/go-test/deep/badge.svg?branch=master)](https://coveralls.io/github/go-test/deep?branch=master) [![GoDoc](https://godoc.org/github.com/go-test/deep?status.svg)](https://godoc.org/github.com/go-test/deep) + +This package provides a single function: `deep.Equal`. It's like [reflect.DeepEqual](http://golang.org/pkg/reflect/#DeepEqual) but much friendlier to humans (or any sentient being) for two reason: + +* `deep.Equal` returns a list of differences +* `deep.Equal` does not compare unexported fields (by default) + +`reflect.DeepEqual` is good (like all things Golang!), but it's a game of [Hunt the Wumpus](https://en.wikipedia.org/wiki/Hunt_the_Wumpus). For large maps, slices, and structs, finding the difference is difficult. + +`deep.Equal` doesn't play games with you, it lists the differences: + +```go +package main_test + +import ( + "testing" + "github.com/go-test/deep" +) + +type T struct { + Name string + Numbers []float64 +} + +func TestDeepEqual(t *testing.T) { + // Can you spot the difference? + t1 := T{ + Name: "Isabella", + Numbers: []float64{1.13459, 2.29343, 3.010100010}, + } + t2 := T{ + Name: "Isabella", + Numbers: []float64{1.13459, 2.29843, 3.010100010}, + } + + if diff := deep.Equal(t1, t2); diff != nil { + t.Error(diff) + } +} +``` + + +``` +$ go test +--- FAIL: TestDeepEqual (0.00s) + main_test.go:25: [Numbers.slice[1]: 2.29343 != 2.29843] +``` + +The difference is in `Numbers.slice[1]`: the two values aren't equal using Go `==`. diff --git a/vendor/github.com/go-test/deep/deep.go b/vendor/github.com/go-test/deep/deep.go new file mode 100644 index 0000000000..4ea14cb04e --- /dev/null +++ b/vendor/github.com/go-test/deep/deep.go @@ -0,0 +1,352 @@ +// Package deep provides function deep.Equal which is like reflect.DeepEqual but +// returns a list of differences. This is helpful when comparing complex types +// like structures and maps. +package deep + +import ( + "errors" + "fmt" + "log" + "reflect" + "strings" +) + +var ( + // FloatPrecision is the number of decimal places to round float values + // to when comparing. + FloatPrecision = 10 + + // MaxDiff specifies the maximum number of differences to return. + MaxDiff = 10 + + // MaxDepth specifies the maximum levels of a struct to recurse into. + MaxDepth = 10 + + // LogErrors causes errors to be logged to STDERR when true. + LogErrors = false + + // CompareUnexportedFields causes unexported struct fields, like s in + // T{s int}, to be comparsed when true. + CompareUnexportedFields = false +) + +var ( + // ErrMaxRecursion is logged when MaxDepth is reached. + ErrMaxRecursion = errors.New("recursed to MaxDepth") + + // ErrTypeMismatch is logged when Equal passed two different types of values. + ErrTypeMismatch = errors.New("variables are different reflect.Type") + + // ErrNotHandled is logged when a primitive Go kind is not handled. + ErrNotHandled = errors.New("cannot compare the reflect.Kind") +) + +type cmp struct { + diff []string + buff []string + floatFormat string +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +// Equal compares variables a and b, recursing into their structure up to +// MaxDepth levels deep, and returns a list of differences, or nil if there are +// none. Some differences may not be found if an error is also returned. +// +// If a type has an Equal method, like time.Equal, it is called to check for +// equality. +func Equal(a, b interface{}) []string { + aVal := reflect.ValueOf(a) + bVal := reflect.ValueOf(b) + c := &cmp{ + diff: []string{}, + buff: []string{}, + floatFormat: fmt.Sprintf("%%.%df", FloatPrecision), + } + if a == nil && b == nil { + return nil + } else if a == nil && b != nil { + c.saveDiff(b, "") + } else if a != nil && b == nil { + c.saveDiff(a, "") + } + if len(c.diff) > 0 { + return c.diff + } + + c.equals(aVal, bVal, 0) + if len(c.diff) > 0 { + return c.diff // diffs + } + return nil // no diffs +} + +func (c *cmp) equals(a, b reflect.Value, level int) { + if level > MaxDepth { + logError(ErrMaxRecursion) + return + } + + // Check if one value is nil, e.g. T{x: *X} and T.x is nil + if !a.IsValid() || !b.IsValid() { + if a.IsValid() && !b.IsValid() { + c.saveDiff(a.Type(), "") + } else if !a.IsValid() && b.IsValid() { + c.saveDiff("", b.Type()) + } + return + } + + // If differenet types, they can't be equal + aType := a.Type() + bType := b.Type() + if aType != bType { + c.saveDiff(aType, bType) + logError(ErrTypeMismatch) + return + } + + // Primitive https://golang.org/pkg/reflect/#Kind + aKind := a.Kind() + bKind := b.Kind() + + // If both types implement the error interface, compare the error strings. + // This must be done before dereferencing because the interface is on a + // pointer receiver. + if aType.Implements(errorType) && bType.Implements(errorType) { + if a.Elem().IsValid() && b.Elem().IsValid() { // both err != nil + aString := a.MethodByName("Error").Call(nil)[0].String() + bString := b.MethodByName("Error").Call(nil)[0].String() + if aString != bString { + c.saveDiff(aString, bString) + } + return + } + } + + // Dereference pointers and interface{} + if aElem, bElem := (aKind == reflect.Ptr || aKind == reflect.Interface), + (bKind == reflect.Ptr || bKind == reflect.Interface); aElem || bElem { + + if aElem { + a = a.Elem() + } + + if bElem { + b = b.Elem() + } + + c.equals(a, b, level+1) + return + } + + // Types with an Equal(), like time.Time. + eqFunc := a.MethodByName("Equal") + if eqFunc.IsValid() { + retVals := eqFunc.Call([]reflect.Value{b}) + if !retVals[0].Bool() { + c.saveDiff(a, b) + } + return + } + + switch aKind { + + ///////////////////////////////////////////////////////////////////// + // Iterable kinds + ///////////////////////////////////////////////////////////////////// + + case reflect.Struct: + /* + The variables are structs like: + type T struct { + FirstName string + LastName string + } + Type = .T, Kind = reflect.Struct + + Iterate through the fields (FirstName, LastName), recurse into their values. + */ + for i := 0; i < a.NumField(); i++ { + if aType.Field(i).PkgPath != "" && !CompareUnexportedFields { + continue // skip unexported field, e.g. s in type T struct {s string} + } + + c.push(aType.Field(i).Name) // push field name to buff + + // Get the Value for each field, e.g. FirstName has Type = string, + // Kind = reflect.String. + af := a.Field(i) + bf := b.Field(i) + + // Recurse to compare the field values + c.equals(af, bf, level+1) + + c.pop() // pop field name from buff + + if len(c.diff) >= MaxDiff { + break + } + } + case reflect.Map: + /* + The variables are maps like: + map[string]int{ + "foo": 1, + "bar": 2, + } + Type = map[string]int, Kind = reflect.Map + + Or: + type T map[string]int{} + Type = .T, Kind = reflect.Map + + Iterate through the map keys (foo, bar), recurse into their values. + */ + + if a.IsNil() || b.IsNil() { + if a.IsNil() && !b.IsNil() { + c.saveDiff("", b) + } else if !a.IsNil() && b.IsNil() { + c.saveDiff(a, "") + } + return + } + + if a.Pointer() == b.Pointer() { + return + } + + for _, key := range a.MapKeys() { + c.push(fmt.Sprintf("map[%s]", key)) + + aVal := a.MapIndex(key) + bVal := b.MapIndex(key) + if bVal.IsValid() { + c.equals(aVal, bVal, level+1) + } else { + c.saveDiff(aVal, "") + } + + c.pop() + + if len(c.diff) >= MaxDiff { + return + } + } + + for _, key := range b.MapKeys() { + if aVal := a.MapIndex(key); aVal.IsValid() { + continue + } + + c.push(fmt.Sprintf("map[%s]", key)) + c.saveDiff("", b.MapIndex(key)) + c.pop() + if len(c.diff) >= MaxDiff { + return + } + } + case reflect.Array: + n := a.Len() + for i := 0; i < n; i++ { + c.push(fmt.Sprintf("array[%d]", i)) + c.equals(a.Index(i), b.Index(i), level+1) + c.pop() + if len(c.diff) >= MaxDiff { + break + } + } + case reflect.Slice: + if a.IsNil() || b.IsNil() { + if a.IsNil() && !b.IsNil() { + c.saveDiff("", b) + } else if !a.IsNil() && b.IsNil() { + c.saveDiff(a, "") + } + return + } + + if a.Pointer() == b.Pointer() { + return + } + + aLen := a.Len() + bLen := b.Len() + n := aLen + if bLen > aLen { + n = bLen + } + for i := 0; i < n; i++ { + c.push(fmt.Sprintf("slice[%d]", i)) + if i < aLen && i < bLen { + c.equals(a.Index(i), b.Index(i), level+1) + } else if i < aLen { + c.saveDiff(a.Index(i), "") + } else { + c.saveDiff("", b.Index(i)) + } + c.pop() + if len(c.diff) >= MaxDiff { + break + } + } + + ///////////////////////////////////////////////////////////////////// + // Primitive kinds + ///////////////////////////////////////////////////////////////////// + + case reflect.Float32, reflect.Float64: + // Avoid 0.04147685731961082 != 0.041476857319611 + // 6 decimal places is close enough + aval := fmt.Sprintf(c.floatFormat, a.Float()) + bval := fmt.Sprintf(c.floatFormat, b.Float()) + if aval != bval { + c.saveDiff(a.Float(), b.Float()) + } + case reflect.Bool: + if a.Bool() != b.Bool() { + c.saveDiff(a.Bool(), b.Bool()) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if a.Int() != b.Int() { + c.saveDiff(a.Int(), b.Int()) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if a.Uint() != b.Uint() { + c.saveDiff(a.Uint(), b.Uint()) + } + case reflect.String: + if a.String() != b.String() { + c.saveDiff(a.String(), b.String()) + } + + default: + logError(ErrNotHandled) + } +} + +func (c *cmp) push(name string) { + c.buff = append(c.buff, name) +} + +func (c *cmp) pop() { + if len(c.buff) > 0 { + c.buff = c.buff[0 : len(c.buff)-1] + } +} + +func (c *cmp) saveDiff(aval, bval interface{}) { + if len(c.buff) > 0 { + varName := strings.Join(c.buff, ".") + c.diff = append(c.diff, fmt.Sprintf("%s: %v != %v", varName, aval, bval)) + } else { + c.diff = append(c.diff, fmt.Sprintf("%v != %v", aval, bval)) + } +} + +func logError(err error) { + if LogErrors { + log.Println(err) + } +} diff --git a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/alert_configurations.go b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/alert_configurations.go new file mode 100644 index 0000000000..c8d95c143b --- /dev/null +++ b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/alert_configurations.go @@ -0,0 +1,309 @@ +package mongodbatlas + +import ( + "context" + "fmt" + "net/http" +) + +const alertConfigurationPath = "groups/%s/alertConfigs" + +// AlertConfigurationsService is an interface of the Alert Configuration +// endpoints of the MongoDB Atlas API. +// See more: hhttps://docs.atlas.mongodb.com/reference/api/alert-configurations +type AlertConfigurationsService interface { + Create(context.Context, string, *AlertConfiguration) (*AlertConfiguration, *Response, error) + EnableAnAlertConfig(context.Context, string, string, *bool) (*AlertConfiguration, *Response, error) + GetAnAlertConfig(context.Context, string, string) (*AlertConfiguration, *Response, error) + GetOpenAlertsConfig(context.Context, string, string) ([]AlertConfiguration, *Response, error) + List(context.Context, string, *ListOptions) ([]AlertConfiguration, *Response, error) + Update(context.Context, string, string, *AlertConfiguration) (*AlertConfiguration, *Response, error) + Delete(context.Context, string, string) (*Response, error) +} + +// AlertConfigurationsServiceOp handles communication with the AlertConfiguration related methods +// of the MongoDB Atlas API +type AlertConfigurationsServiceOp struct { + client *Client +} + +var _ AlertConfigurationsService = &AlertConfigurationsServiceOp{} + +// AlertConfiguration represents MongoDB Alert Configuration. +type AlertConfiguration struct { + ID string `json:"id,omitempty"` // Unique identifier. + GroupID string `json:"groupID,omitempty"` // Unique identifier of the project that owns this alert configuration. + AlertConfigID string `json:"alertConfigId,omitempty"` // ID of the alert configuration that triggered this alert. + EventTypeName string `json:"eventTypeName,omitempty"` // The type of event that will trigger an alert. + Created string `json:"created,omitempty"` // Timestamp in ISO 8601 date and time format in UTC when this alert configuration was created. + Status string `json:"status,omitempty"` // The current state of the alert. Possible values are: TRACKING, OPEN, CLOSED, CANCELLED + AcknowledgedUntil string `json:"acknowledgedUntil,omitempty"` // The date through which the alert has been acknowledged. Will not be present if the alert has never been acknowledged. + AcknowledgementComment string `json:"acknowledgementComment,omitempty"` // The comment left by the user who acknowledged the alert. Will not be present if the alert has never been acknowledged. + AcknowledgingUsername string `json:"acknowledgingUsername,omitempty"` // The username of the user who acknowledged the alert. Will not be present if the alert has never been acknowledged. + Updated string `json:"updated,omitempty"` // Timestamp in ISO 8601 date and time format in UTC when this alert configuration was last updated. + Resolved string `json:"resolved,omitempty"` // When the alert was closed. Only present if the status is CLOSED. + LastNotified string `json:"lastNotified,omitempty"` // When the last notification was sent for this alert. Only present if notifications have been sent. + HostnameAndPort string `json:"hostnameAndPort,omitempty"` // The hostname and port of each host to which the alert applies. Only present for alerts of type HOST, HOST_METRIC, and REPLICA_SET. + HostID string `json:"hostId,omitempty"` // ID of the host to which the metric pertains. Only present for alerts of type HOST, HOST_METRIC, and REPLICA_SET. + ReplicaSetName string `json:"replicaSetName,omitempty"` // Name of the replica set. Only present for alerts of type HOST, HOST_METRIC, BACKUP, and REPLICA_SET. + MetricName string `json:"metricName,omitempty"` // The name of the measurement whose value went outside the threshold. Only present if eventTypeName is set to OUTSIDE_METRIC_THRESHOLD. + Enabled *bool `json:"enabled,omitempty"` // If omitted, the configuration is disabled. + ClusterID string `json:"clusterId,omitempty"` // The ID of the cluster to which this alert applies. Only present for alerts of type BACKUP, REPLICA_SET, and CLUSTER. + ClusterName string `json:"clusterName,omitempty"` // The name the cluster to which this alert applies. Only present for alerts of type BACKUP, REPLICA_SET, and CLUSTER. + SourceTypeName string `json:"sourceTypeName,omitempty"` // For alerts of the type BACKUP, the type of server being backed up. + CurrentValue *CurrentValue `json:"currentValue,omitempty"` // CurrentValue represents current value of the metric that triggered the alert. Only present for alerts of type HOST_METRIC. + Matchers []Matcher `json:"matchers,omitempty"` // You can filter using the matchers array only when the EventTypeName specifies an event for a host, replica set, or sharded cluster. + MetricThreshold *MetricThreshold `json:"metricThreshold,omitempty"` // MetricThreshold causes an alert to be triggered. + Notifications []Notification `json:"notifications,omitempty"` // Notifications are sending when an alert condition is detected. +} + +// Matcher represents the Rules to apply when matching an object against this alert configuration. +// Only entities that match all these rules are checked for an alert condition. +type Matcher struct { + FieldName string `json:"fieldName,omitempty"` // Name of the field in the target object to match on. + Operator string `json:"operator,omitempty"` // The operator to test the field’s value. + Value string `json:"value,omitempty"` // Value to test with the specified operator. +} + +// MetricThreshold causes an alert to be triggered. Required if "eventTypeName" : "OUTSIDE_METRIC_THRESHOLD". +type MetricThreshold struct { + MetricName string `json:"metricName,omitempty"` // Name of the metric to check. + Operator string `json:"operator,omitempty"` // Operator to apply when checking the current metric value against the threshold value. + Threshold float64 `json:"threshold,omitempty"` // Threshold value outside of which an alert will be triggered. + Units string `json:"units,omitempty"` // The units for the threshold value. + Mode string `json:"mode,omitempty"` // This must be set to AVERAGE. Atlas computes the current metric value as an average. +} + +// Notification sends when an alert condition is detected. +type Notification struct { + APIToken string `json:"apiToken,omitempty"` // Slack API token or Bot token. Populated for the SLACK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. + ChannelName string `json:"channelName,omitempty"` // Slack channel name. Populated for the SLACK notifications type. + DatadogAPIKey string `json:"datadogApiKey,omitempty"` // Datadog API Key. Found in the Datadog dashboard. Populated for the DATADOG notifications type. + DatadogRegion string `json:"datadogRegion,omitempty"` // Region that indicates which API URL to use + DelayMin *int `json:"delayMin,omitempty"` // Number of minutes to wait after an alert condition is detected before sending out the first notification. + EmailAddress string `json:"emailAddress,omitempty"` // Email address to which alert notifications are sent. Populated for the EMAIL notifications type. + EmailEnabled *bool `json:"emailEnabled,omitempty"` // Flag indicating if email notifications should be sent. Populated for ORG, GROUP, and USER notifications types. + FlowdockAPIToken string `json:"flowdockApiToken,omitempty"` // The Flowdock personal API token. Populated for the FLOWDOCK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. + FlowName string `json:"flowName,omitempty"` // Flowdock flow name in lower-case letters. + IntervalMin int `json:"intervalMin,omitempty"` // Number of minutes to wait between successive notifications for unacknowledged alerts that are not resolved. + MobileNumber string `json:"mobileNumber,omitempty"` // Mobile number to which alert notifications are sent. Populated for the SMS notifications type. + OpsGenieAPIKey string `json:"opsGenieApiKey,omitempty"` // Opsgenie API Key. Populated for the OPS_GENIE notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. + OpsGenieRegion string `json:"opsGenieRegion,omitempty"` // Region that indicates which API URL to use. + OrgName string `json:"orgName,omitempty"` // Flowdock organization name in lower-case letters. This is the name that appears after www.flowdock.com/app/ in the URL string. Populated for the FLOWDOCK notifications type. + ServiceKey string `json:"serviceKey,omitempty"` // PagerDuty service key. Populated for the PAGER_DUTY notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. + SMSEnabled *bool `json:"smsEnabled,omitempty"` // Flag indicating if text message notifications should be sent. Populated for ORG, GROUP, and USER notifications types. + TeamID string `json:"teamId,omitempty"` // Unique identifier of a team. + TypeName string `json:"typeName,omitempty"` // Type of alert notification. + Username string `json:"username,omitempty"` // Name of the Atlas user to which to send notifications. Only a user in the project that owns the alert configuration is allowed here. Populated for the USER notifications type. + VictorOpsAPIKey string `json:"victorOpsApiKey,omitempty"` // VictorOps API key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. + VictorOpsRoutingKey string `json:"victorOpsRoutingKey,omitempty"` // VictorOps routing key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. +} + +// AlertConfigurationsResponse is the response from the AlertConfigurationsService.List. +type AlertConfigurationsResponse struct { + Links []*Link `json:"links"` + Results []AlertConfiguration `json:"results"` + TotalCount int `json:"totalCount"` +} + +// CurrentValue represents current value of the metric that triggered the alert. Only present for alerts of type HOST_METRIC. +type CurrentValue struct { + Number *float64 `json:"number,omitempty"` // The value of the metric. + Units string `json:"units,omitempty"` // The units for the value. Depends on the type of metric. +} + +// Create creates an alert configuration for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-create-config/ +func (s *AlertConfigurationsServiceOp) Create(ctx context.Context, groupID string, createReq *AlertConfiguration) (*AlertConfiguration, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + if createReq == nil { + return nil, nil, NewArgError("createReq", "cannot be nil") + } + + path := fmt.Sprintf(alertConfigurationPath, groupID) + + req, err := s.client.NewRequest(ctx, http.MethodPost, path, createReq) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfiguration) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} + +// EnableAnAlertConfig Enables/disables the alert configuration specified to {ALERT-CONFIG-ID} for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-enable-disable-config/ +func (s *AlertConfigurationsServiceOp) EnableAnAlertConfig(ctx context.Context, groupID, alertConfigID string, enabled *bool) (*AlertConfiguration, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + if alertConfigID == "" { + return nil, nil, NewArgError("alertConfigID", "must be set") + } + + basePath := fmt.Sprintf(alertConfigurationPath, groupID) + path := fmt.Sprintf("%s/%s", basePath, alertConfigID) + + req, err := s.client.NewRequest(ctx, http.MethodPatch, path, AlertConfiguration{Enabled: enabled}) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfiguration) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} + +// GetAnAlertConfig gets the alert configuration specified to {ALERT-CONFIG-ID} for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-get-config/ +func (s *AlertConfigurationsServiceOp) GetAnAlertConfig(ctx context.Context, groupID, alertConfigID string) (*AlertConfiguration, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + if alertConfigID == "" { + return nil, nil, NewArgError("alertConfigID", "must be set") + } + + basePath := fmt.Sprintf(alertConfigurationPath, groupID) + path := fmt.Sprintf("%s/%s", basePath, alertConfigID) + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfiguration) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} + +// GetOpenAlertsConfig gets all open alerts for the alert configuration specified to {ALERT-CONFIG-ID} for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-get-open-alerts/ +func (s *AlertConfigurationsServiceOp) GetOpenAlertsConfig(ctx context.Context, groupID, alertConfigID string) ([]AlertConfiguration, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + if alertConfigID == "" { + return nil, nil, NewArgError("alertConfigID", "must be set") + } + + basePath := fmt.Sprintf(alertConfigurationPath, groupID) + path := fmt.Sprintf("%s/%s/alerts", basePath, alertConfigID) + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfigurationsResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + if l := root.Links; l != nil { + resp.Links = l + } + return root.Results, resp, err +} + +// List gets all alert configurations for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-get-all-configs/ +func (s *AlertConfigurationsServiceOp) List(ctx context.Context, groupID string, listOptions *ListOptions) ([]AlertConfiguration, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + path := fmt.Sprintf(alertConfigurationPath, groupID) + + //Add query params from listOptions + path, err := setListOptions(path, listOptions) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfigurationsResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Results, resp, nil +} + +// Update the alert configuration specified to {ALERT-CONFIG-ID} for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-update-config/ +func (s *AlertConfigurationsServiceOp) Update(ctx context.Context, groupID, alertConfigID string, updateReq *AlertConfiguration) (*AlertConfiguration, *Response, error) { + if updateReq == nil { + return nil, nil, NewArgError("updateRequest", "cannot be nil") + } + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + if alertConfigID == "" { + return nil, nil, NewArgError("alertConfigID", "must be set") + } + + basePath := fmt.Sprintf(alertConfigurationPath, groupID) + path := fmt.Sprintf("%s/%s", basePath, alertConfigID) + + req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateReq) + if err != nil { + return nil, nil, err + } + + root := new(AlertConfiguration) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} + +// Delete the alert configuration specified to {ALERT-CONFIG-ID} for the project associated to {GROUP-ID}. +// See more: https://docs.atlas.mongodb.com/reference/api/alert-configurations-delete-config/ +func (s *AlertConfigurationsServiceOp) Delete(ctx context.Context, groupID, alertConfigID string) (*Response, error) { + if groupID == "" { + return nil, NewArgError("groupID", "must be set") + } + if alertConfigID == "" { + return nil, NewArgError("alertConfigID", "must be set") + } + + basePath := fmt.Sprintf(alertConfigurationPath, groupID) + path := fmt.Sprintf("%s/%s", basePath, alertConfigID) + + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + + return resp, err +} diff --git a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/database_users.go b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/database_users.go index c34ebb2b99..3dbb1a9edf 100644 --- a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/database_users.go +++ b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/database_users.go @@ -37,13 +37,20 @@ type Role struct { // DatabaseUser represents MongoDB users in your cluster. type DatabaseUser struct { - Roles []Role `json:"roles,omitempty"` - GroupID string `json:"groupId,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - DatabaseName string `json:"databaseName,omitempty"` - LDAPAuthType string `json:"ldapAuthType,omitempty"` - DeleteAfterDate string `json:"deleteAfterDate,omitempty"` + Roles []Role `json:"roles,omitempty"` + GroupID string `json:"groupId,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + DatabaseName string `json:"databaseName,omitempty"` + Labels []Label `json:"labels,omitempty"` + LDAPAuthType string `json:"ldapAuthType,omitempty"` + DeleteAfterDate string `json:"deleteAfterDate,omitempty"` +} + +// Label containing key-value pairs that tag and categorize the database user +type Label struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` } // databaseUserListResponse is the response from the DatabaseUserService.List. diff --git a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/mongodbatlas.go b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/mongodbatlas.go index 127e6ce3a9..38df0cd043 100644 --- a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/mongodbatlas.go +++ b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/mongodbatlas.go @@ -50,6 +50,7 @@ type Client struct { AtlasUsers AtlasUsersService GlobalClusters GlobalClustersService Auditing AuditingsService + AlertConfigurations AlertConfigurationsService onRequestCompleted RequestCompletionCallback } @@ -159,6 +160,7 @@ func NewClient(httpClient *http.Client) *Client { c.AtlasUsers = &AtlasUsersServiceOp{client: c} c.GlobalClusters = &GlobalClustersServiceOp{client: c} c.Auditing = &AuditingsServiceOp{client: c} + c.AlertConfigurations = &AlertConfigurationsServiceOp{client: c} return c } diff --git a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/project_ip_whitelist.go b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/project_ip_whitelist.go index ec31b65221..82d4ea069f 100644 --- a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/project_ip_whitelist.go +++ b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/project_ip_whitelist.go @@ -102,7 +102,7 @@ func (s *ProjectIPWhitelistServiceOp) Get(ctx context.Context, groupID string, w func (s *ProjectIPWhitelistServiceOp) List(ctx context.Context, groupID string, listOptions *ListOptions) ([]ProjectIPWhitelist, *Response, error) { path := fmt.Sprintf(projectIPWhitelistPath, groupID) - //Add query params from listOptions + // Add query params from listOptions path, err := setListOptions(path, listOptions) if err != nil { return nil, nil, err diff --git a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/teams.go b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/teams.go index e87732d6da..8b5b61ae68 100644 --- a/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/teams.go +++ b/vendor/github.com/mongodb/go-client-mongodb-atlas/mongodbatlas/teams.go @@ -21,7 +21,7 @@ type TeamsService interface { Create(context.Context, string, *Team) (*Team, *Response, error) Rename(context.Context, string, string, string) (*Team, *Response, error) UpdateTeamRoles(context.Context, string, string, *TeamUpdateRoles) ([]TeamRoles, *Response, error) - AddUserToTeam(context.Context, string, string, string) ([]AtlasUser, *Response, error) + AddUsersToTeam(context.Context, string, string, []string) ([]AtlasUser, *Response, error) RemoveUserToTeam(context.Context, string, string, string) (*Response, error) RemoveTeamFromOrganization(context.Context, string, string) (*Response, error) RemoveTeamFromProject(context.Context, string, string) (*Response, error) @@ -251,19 +251,22 @@ func (s *TeamsServiceOp) UpdateTeamRoles(ctx context.Context, orgID string, team return root.Results, resp, nil } -//AddUserToTeam adds a user from the organization associated with {ORG-ID} to the team with ID {TEAM-ID}. +//AddUsersToTeam adds a users from the organization associated with {ORG-ID} to the team with ID {TEAM-ID}. //See more: https://docs.atlas.mongodb.com/reference/api/teams-add-user/ -func (s *TeamsServiceOp) AddUserToTeam(ctx context.Context, orgID, teamID, userID string) ([]AtlasUser, *Response, error) { - if userID == "" { - return nil, nil, NewArgError("userID", "cannot be nil") +func (s *TeamsServiceOp) AddUsersToTeam(ctx context.Context, orgID, teamID string, usersID []string) ([]AtlasUser, *Response, error) { + if len(usersID) < 1 { + return nil, nil, NewArgError("usersID", "cannot empty at leas one userID must be set") } basePath := fmt.Sprintf(teamsBasePath, orgID) path := fmt.Sprintf("%s/%s/users", basePath, teamID) - req, err := s.client.NewRequest(ctx, http.MethodPost, path, map[string]interface{}{ - "id": userID, - }) + users := make([]map[string]interface{}, len(usersID)) + for i, id := range usersID { + users[i] = map[string]interface{}{"id": id} + } + + req, err := s.client.NewRequest(ctx, http.MethodPost, path, users) if err != nil { return nil, nil, err diff --git a/vendor/modules.txt b/vendor/modules.txt index 3c8dcdd437..585aea5e12 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -61,6 +61,8 @@ github.com/blang/semver github.com/davecgh/go-spew/spew # github.com/fatih/color v1.7.0 github.com/fatih/color +# github.com/go-test/deep v1.0.1 +github.com/go-test/deep # github.com/golang/protobuf v1.3.0 github.com/golang/protobuf/proto github.com/golang/protobuf/protoc-gen-go/descriptor @@ -203,7 +205,7 @@ github.com/mitchellh/hashstructure github.com/mitchellh/mapstructure # github.com/mitchellh/reflectwalk v1.0.0 github.com/mitchellh/reflectwalk -# github.com/mongodb/go-client-mongodb-atlas v0.1.2 => ../go-client-mongodb-atlas +# github.com/mongodb/go-client-mongodb-atlas v0.1.3-0.20200110215223-d0530efb030d github.com/mongodb/go-client-mongodb-atlas/mongodbatlas # github.com/mwielbut/pointy v1.1.0 github.com/mwielbut/pointy diff --git a/website/docs/r/maintenance_window.html.markdown b/website/docs/r/maintenance_window.html.markdown new file mode 100644 index 0000000000..5503e98766 --- /dev/null +++ b/website/docs/r/maintenance_window.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "mongodbatlas" +page_title: "MongoDB Atlas: maintenance_window" +sidebar_current: "docs-mongodbatlas-resource-maintenance_window" +description: |- + Provides an Maintenance Window resource. +--- + +# mongodbatlas_maintenance_window + +`mongodbatlas_maintenance_window` provides a resource to schedule a maintenance window for your MongoDB Atlas Project and/or set to defer a scheduled maintenance up to two times. + +-> **NOTE:** Groups and projects are synonymous terms. You may find `groupId` in the official documentation. + +## Maintenance Window Considerations: +- Urgent Maintenance Activities Cannot Wait: Urgent maintenance activities such as security patches cannot wait for your chosen window. Atlas will start those maintenance activities when needed. + +Once maintenance is scheduled for your cluster, you cannot change your maintenance window until the current maintenance efforts have completed. +- Maintenance Requires Replica Set Elections: Atlas performs maintenance the same way as the manual maintenance procedure. This requires at least one replica set election during the maintenance window per replica set. +- Maintenance Starts As Close to the Hour As Possible: Maintenance always begins as close to the scheduled hour as possible, but in-progress cluster updates or expected system issues could delay the start time. + + +## Example Usage + +```hcl + resource "mongodbatlas_maintenance_window" "test" { + project_id = "" + day_of_week = 3 + hour_of_day = 4 + } + +``` + +```hcl + resource "mongodbatlas_maintenance_window" "test" { + project_id = "" + defer = true + } +``` + +## Argument Reference + +* `project_id` - The unique identifier of the project for the Maintenance Window. +* `day_of_week` - Day of the week when you would like the maintenance window to start as a 1-based integer: S=1, M=2, T=3, W=4, T=5, F=6, S=7. +* `hour_of_day` - Hour of the day when you would like the maintenance window to start. This parameter uses the 24-hour clock, where midnight is 0, noon is 12 (Time zone is UTC). +* `start_asap` - Flag indicating whether project maintenance has been directed to start immediately. If you request that maintenance begin immediately, this field returns true from the time the request was made until the time the maintenance event completes. +* `number_of_deferrals` - Number of times the current maintenance event for this project has been deferred, you can set a maximum of 2 deferrals. +* `defer` - Defer maintenance for the given project for one week. + +-> **NOTE:** The `start_asap` attribute can't be used because of breaks the Terraform flow, but you can enable via API. + +## Import + +Maintenance Window entries can be imported using project project_id, in the format `PROJECTID`, e.g. + +``` +$ terraform import mongodbatlas_maintenance_window.test 5d0f1f73cf09a29120e173cf +``` + +For more information see: [MongoDB Atlas API Reference.](https://docs.atlas.mongodb.com/reference/api/maintenance-windows/) \ No newline at end of file diff --git a/website/mongodbatlas.erb b/website/mongodbatlas.erb index 1b9ed52512..8ae86de367 100644 --- a/website/mongodbatlas.erb +++ b/website/mongodbatlas.erb @@ -84,6 +84,9 @@ > mongodbatlas_private_ip_mode + > + mongodbatlas_maintenance_window +