From 92cc2a32a2e7bf2a09cfda67a1dc5c8c331ce396 Mon Sep 17 00:00:00 2001 From: Jacob Hull Date: Mon, 26 Sep 2022 09:18:40 -0700 Subject: [PATCH] feat: add support for enterprise and child organizations This begins support for terraform management of enterprise and child organizations: * Allows the provider to accept an enterprise key to indicate an enterprise organization. * Enterprise organizations can attach and detach child organizations. Ref: LOG-13116 Signed-off-by: Jacob Hull --- logdna/common_test.go | 10 +- logdna/data_source_alert.go | 7 + logdna/data_source_alert_test.go | 16 + logdna/meta.go | 89 +++ logdna/provider.go | 43 +- logdna/provider_test.go | 3 +- logdna/request.go | 51 +- logdna/request_test.go | 28 +- logdna/request_types.go | 21 + logdna/resource_alert.go | 9 +- logdna/resource_alert_test.go | 13 + logdna/resource_archive.go | 7 + logdna/resource_category.go | 335 +++++----- logdna/resource_category_test.go | 172 ++--- logdna/resource_child_organization.go | 188 ++++++ logdna/resource_child_organization_test.go | 51 ++ logdna/resource_index_rate_alert.go | 355 +++++------ logdna/resource_index_rate_alert_test.go | 695 +++++++++++---------- logdna/resource_ingestion_exclusion.go | 9 +- logdna/resource_key.go | 7 + logdna/resource_key_test.go | 18 + logdna/resource_member.go | 8 +- logdna/resource_member_test.go | 18 + logdna/resource_stream_config.go | 7 + logdna/resource_stream_exclusion.go | 9 +- logdna/resource_view.go | 13 +- logdna/resource_view_test.go | 14 + logdna/response_types.go | 6 + 28 files changed, 1406 insertions(+), 796 deletions(-) create mode 100644 logdna/meta.go create mode 100644 logdna/resource_child_organization.go create mode 100644 logdna/resource_child_organization_test.go diff --git a/logdna/common_test.go b/logdna/common_test.go index 09e5626..b6cc3f3 100644 --- a/logdna/common_test.go +++ b/logdna/common_test.go @@ -5,8 +5,8 @@ import ( "regexp" "strings" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const tmplPc = `provider "logdna" { @@ -106,15 +106,19 @@ func fmtTestConfigResource(objTyp, rsName string, pcArgs []string, rsArgs map[st } func fmtProviderBlock(args ...string) string { - opts := []string{serviceKey, ""} + opts := []string{serviceKey, "", "regular"} copy(opts, args) - sk, ul := opts[0], opts[1] + sk, ul, tp := opts[0], opts[1], opts[2] pcCfg := fmt.Sprintf(`servicekey = %q`, sk) if ul != "" { pcCfg = pcCfg + fmt.Sprintf("\n\turl = %q", ul) } + if tp != "" { + pcCfg = pcCfg + fmt.Sprintf("\n\ttype = %q", tp) + } + return fmt.Sprintf(tmplPc, pcCfg) } diff --git a/logdna/data_source_alert.go b/logdna/data_source_alert.go index c22a740..0ff7635 100644 --- a/logdna/data_source_alert.go +++ b/logdna/data_source_alert.go @@ -26,6 +26,13 @@ var alertProps = map[string]*schema.Schema{ "triggerlimit": intSchema, } +var _ = registerTerraform(TerraformInfo{ + name: "logdna_alert", + orgType: OrgTypeRegular, + terraformType: TerraformTypeDataSource, + schema: dataSourceAlert(), +}) + func dataSourceAlertRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics diff --git a/logdna/data_source_alert_test.go b/logdna/data_source_alert_test.go index d54cc61..13238d1 100644 --- a/logdna/data_source_alert_test.go +++ b/logdna/data_source_alert_test.go @@ -2,6 +2,7 @@ package logdna import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,6 +15,21 @@ data "logdna_alert" "remote" { } ` +func TestDataAlert_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + alertConfig := fmtTestConfigResource("alert", "test", pcArgs, alertDefaults, nilOpt, nilLst) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf("%s\n%s", alertConfig, ds), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_alert\" resource"), + }, + }, + }) +} + func TestDataAlert_BulkChannels(t *testing.T) { emArgs := map[string]map[string]string{ "email_channel": cloneDefaults(chnlDefaults["email_channel"]), diff --git a/logdna/meta.go b/logdna/meta.go new file mode 100644 index 0000000..d9adc5f --- /dev/null +++ b/logdna/meta.go @@ -0,0 +1,89 @@ +package logdna + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type OrgType string +type TerraformType string +type TerraformInfo struct { + name string + orgType OrgType + terraformType TerraformType + schema *schema.Resource +} + +const ( + OrgTypeRegular OrgType = "regular" + OrgTypeEnterprise OrgType = "enterprise" +) + +const ( + TerraformTypeResource TerraformType = "resource" + TerraformTypeDataSource TerraformType = "data source" +) + +var terraformRegistry []TerraformInfo + +func registerTerraform(info TerraformInfo) *TerraformInfo { + terraformRegistry = append(terraformRegistry, info) + infoPt := &terraformRegistry[len(terraformRegistry)-1] + + if infoPt.schema.CreateContext != nil { + infoPt.schema.CreateContext = buildTerraformFunc(infoPt.schema.CreateContext, infoPt) + } + if infoPt.schema.ReadContext != nil { + infoPt.schema.ReadContext = buildTerraformFunc(infoPt.schema.ReadContext, infoPt) + } + if infoPt.schema.UpdateContext != nil { + infoPt.schema.UpdateContext = buildTerraformFunc(infoPt.schema.UpdateContext, infoPt) + } + if infoPt.schema.DeleteContext != nil { + infoPt.schema.DeleteContext = buildTerraformFunc(infoPt.schema.DeleteContext, infoPt) + } + + return infoPt +} + +func filterRegistry(terraformType TerraformType) []TerraformInfo { + newSlice := []TerraformInfo{} + + for _, info := range terraformRegistry { + if info.terraformType == terraformType { + newSlice = append(newSlice, info) + } + } + + return newSlice +} + +func buildSchemaMap(a []TerraformInfo) map[string]*schema.Resource { + m := make(map[string]*schema.Resource) + + for _, e := range a { + m[e.name] = e.schema + } + + return m +} + +func buildTerraformFunc(contextFunc func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics, info *TerraformInfo) func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { + return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + pc := m.(*providerConfig) + + if pc.orgType != info.orgType { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Only %s organizations can instantiate a \"%s\" %s", info.orgType, info.name, info.terraformType), + }) + return diags + } + + return contextFunc(ctx, d, m) + } +} diff --git a/logdna/provider.go b/logdna/provider.go index 3d08946..0dd71cb 100644 --- a/logdna/provider.go +++ b/logdna/provider.go @@ -5,10 +5,12 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) type providerConfig struct { serviceKey string + orgType OrgType baseURL string httpClient *http.Client } @@ -18,8 +20,15 @@ func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "servicekey": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Sensitive: true, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Default: "regular", + ValidateFunc: validation.StringInSlice([]string{"regular", "enterprise"}, false), }, "url": { Type: schema.TypeString, @@ -27,31 +36,29 @@ func Provider() *schema.Provider { Default: "https://api.logdna.com", }, }, - DataSourcesMap: map[string]*schema.Resource{ - "logdna_alert": dataSourceAlert(), - }, - ResourcesMap: map[string]*schema.Resource{ - "logdna_alert": resourceAlert(), - "logdna_view": resourceView(), - "logdna_category": resourceCategory(), - "logdna_stream_config": resourceStreamConfig(), - "logdna_stream_exclusion": resourceStreamExclusion(), - "logdna_ingestion_exclusion": resourceIngestionExclusion(), - "logdna_archive": resourceArchiveConfig(), - "logdna_key": resourceKey(), - "logdna_index_rate_alert": resourceIndexRateAlert(), - "logdna_member": resourceMember(), - }, - ConfigureFunc: providerConfigure, + DataSourcesMap: buildSchemaMap(filterRegistry(TerraformTypeDataSource)), + ResourcesMap: buildSchemaMap(filterRegistry(TerraformTypeResource)), + ConfigureFunc: providerConfigure, } } func providerConfigure(d *schema.ResourceData) (interface{}, error) { serviceKey := d.Get("servicekey").(string) + orgTypeRaw := d.Get("type").(string) url := d.Get("url").(string) + orgType := OrgTypeRegular + + switch orgTypeRaw { + case "regular": + orgType = OrgTypeRegular + case "enterprise": + orgType = OrgTypeEnterprise + } + return &providerConfig{ serviceKey: serviceKey, + orgType: orgType, baseURL: url, httpClient: &http.Client{Timeout: 15 * time.Second}, }, nil diff --git a/logdna/provider_test.go b/logdna/provider_test.go index e7887e6..c02c950 100644 --- a/logdna/provider_test.go +++ b/logdna/provider_test.go @@ -8,8 +8,9 @@ import ( ) var serviceKey = os.Getenv("SERVICE_KEY") +var enterpriseServiceKey = os.Getenv("ENTERPRISE_SERVICE_KEY") var apiHostUrl = os.Getenv("API_URL") -var globalPcArgs = []string {serviceKey, apiHostUrl} +var globalPcArgs = []string{serviceKey, apiHostUrl} var testAccProviders map[string]*schema.Provider var testAccProvider *schema.Provider diff --git a/logdna/request.go b/logdna/request.go index 6f7aff0..e08802a 100644 --- a/logdna/request.go +++ b/logdna/request.go @@ -18,27 +18,37 @@ type httpClientInterface interface { // Configuration for the HTTP client used to make requests to remote resources type requestConfig struct { - serviceKey string - httpClient httpClientInterface - apiURL string - method string - body interface{} - httpRequest httpRequest - bodyReader bodyReader - jsonMarshal jsonMarshal + serviceKey string + enterpriseKey string + httpClient httpClientInterface + apiURL string + method string + body interface{} + httpRequest httpRequest + bodyReader bodyReader + jsonMarshal jsonMarshal } // newRequestConfig abstracts the struct creation to allow for mocking func newRequestConfig(pc *providerConfig, method string, uri string, body interface{}, mutators ...func(*requestConfig)) *requestConfig { + serviceKey := "" + enterpriseKey := "" + switch pc.orgType { + case OrgTypeRegular: + serviceKey = pc.serviceKey + case OrgTypeEnterprise: + enterpriseKey = pc.serviceKey + } rc := &requestConfig{ - serviceKey: pc.serviceKey, - httpClient: pc.httpClient, - apiURL: fmt.Sprintf("%s%s", pc.baseURL, uri), // uri should have a preceding slash (/) - method: method, - body: body, - httpRequest: http.NewRequest, - bodyReader: ioutil.ReadAll, - jsonMarshal: json.Marshal, + serviceKey: serviceKey, + enterpriseKey: enterpriseKey, + httpClient: pc.httpClient, + apiURL: fmt.Sprintf("%s%s", pc.baseURL, uri), // uri should have a preceding slash (/) + method: method, + body: body, + httpRequest: http.NewRequest, + bodyReader: ioutil.ReadAll, + jsonMarshal: json.Marshal, } // Used during testing only; Allow mutations passed in by tests @@ -63,7 +73,14 @@ func (c *requestConfig) MakeRequest() ([]byte, error) { return nil, err } req.Header.Set("Content-Type", "application/json") - req.Header.Set("servicekey", c.serviceKey) + + if c.serviceKey != "" { + req.Header.Set("servicekey", c.serviceKey) + } + if c.enterpriseKey != "" { + req.Header.Set("enterprise-servicekey", c.enterpriseKey) + } + res, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("error during HTTP request: %s", err) diff --git a/logdna/request_test.go b/logdna/request_test.go index c5cc9c9..ca73975 100644 --- a/logdna/request_test.go +++ b/logdna/request_test.go @@ -41,7 +41,7 @@ func setJSONMarshal(customMarshaller jsonMarshal) func(*requestConfig) { func TestRequest_MakeRequest(t *testing.T) { assert := assert.New(t) - pc := providerConfig{serviceKey: "abc123", httpClient: &http.Client{Timeout: 15 * time.Second}} + pc := providerConfig{serviceKey: "abc123", orgType: OrgTypeRegular, httpClient: &http.Client{Timeout: 15 * time.Second}} resourceID := "test123456" t.Run("Server receives proper method, URL, and headers", func(t *testing.T) { @@ -69,6 +69,32 @@ func TestRequest_MakeRequest(t *testing.T) { assert.Nil(err, "No errors") }) + t.Run("Server receives proper method, URL, and headers for enterprise org", func(t *testing.T) { + enterprisePC := providerConfig{serviceKey: "abc123", orgType: OrgTypeEnterprise, httpClient: &http.Client{Timeout: 15 * time.Second}} + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal("GET", r.Method, "method is correct") + assert.Equal(fmt.Sprintf("/someapi/%s", resourceID), r.URL.String(), "URL is correct") + key, ok := r.Header["Enterprise-Servicekey"] + assert.Equal(true, ok, "enterprise-servicekey header exists") + assert.Equal(1, len(key), "enterprise-servicekey header is correct") + key = r.Header["Content-Type"] + assert.Equal("application/json", key[0], "content-type header is correct") + })) + defer ts.Close() + + enterprisePC.baseURL = ts.URL + + req := newRequestConfig( + &enterprisePC, + "GET", + fmt.Sprintf("/someapi/%s", resourceID), + nil, + ) + + _, err := req.MakeRequest() + assert.Nil(err, "No errors") + }) + t.Run("Reads and decodes response from the server", func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := json.NewEncoder(w).Encode(viewResponse{ViewID: "test123456"}) diff --git a/logdna/request_types.go b/logdna/request_types.go index fc628bf..5e812cf 100644 --- a/logdna/request_types.go +++ b/logdna/request_types.go @@ -81,6 +81,12 @@ type memberPutRequest struct { Groups []string `json:"groups"` } +type childOrgPutRequest struct { + Retention int `json:"retention"` +} + +type childOrgDeleteRequest struct{} + func (view *viewRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostics { // This function pulls from the schema in preparation to JSON marshal var diags diag.Diagnostics @@ -185,6 +191,21 @@ func (member *memberPutRequest) CreateRequestBody(d *schema.ResourceData) diag.D return diags } +func (childOrg *childOrgPutRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostics { + var diags diag.Diagnostics + + // Scalars + childOrg.Retention = d.Get("retention").(int) + + return diags +} + +func (childOrg *childOrgDeleteRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostics { + var diags diag.Diagnostics + + return diags +} + func aggregateAllChannelsFromSchema( d *schema.ResourceData, diags *diag.Diagnostics, diff --git a/logdna/resource_alert.go b/logdna/resource_alert.go index 836ac12..9a1dbeb 100644 --- a/logdna/resource_alert.go +++ b/logdna/resource_alert.go @@ -11,12 +11,17 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_alert", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceAlert(), +}) + func resourceAlertCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics pc := m.(*providerConfig) - alert := alertRequest{} - if diags = alert.CreateRequestBody(d); diags.HasError() { return diags } diff --git a/logdna/resource_alert_test.go b/logdna/resource_alert_test.go index eade13b..1649469 100644 --- a/logdna/resource_alert_test.go +++ b/logdna/resource_alert_test.go @@ -38,6 +38,19 @@ func TestAlert_ErrorResourceName(t *testing.T) { }) } +func TestAlert_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("alert", "new", pcArgs, alertDefaults, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_alert\" resource"), + }, + }, + }) +} + func TestAlert_ErrorsChannel(t *testing.T) { imArgs := map[string]map[string]string{"email_channel": cloneDefaults(chnlDefaults["email_channel"])} imArgs["email_channel"]["immediate"] = `"not a bool"` diff --git a/logdna/resource_archive.go b/logdna/resource_archive.go index 2b22bf9..2c1987b 100644 --- a/logdna/resource_archive.go +++ b/logdna/resource_archive.go @@ -11,6 +11,13 @@ import ( const archiveConfigID = "archive" +var _ = registerTerraform(TerraformInfo{ + name: "logdna_archive", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceArchiveConfig(), +}) + type ibmConfig struct { Bucket string `json:"bucket"` Endpoint string `json:"endpoint"` diff --git a/logdna/resource_category.go b/logdna/resource_category.go index ccf81e0..0217c61 100644 --- a/logdna/resource_category.go +++ b/logdna/resource_category.go @@ -1,212 +1,219 @@ package logdna import ( - "context" - "encoding/json" - "fmt" - "log" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "context" + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_category", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceCategory(), +}) + func resourceCategoryCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - pc := m.(*providerConfig) + var diags diag.Diagnostics + pc := m.(*providerConfig) - // NOTE Type is't a part of a request body - categoryType := d.Get("type").(string) - category := categoryRequest{} + // NOTE Type is't a part of a request body + categoryType := d.Get("type").(string) + category := categoryRequest{} - if diags = category.CreateRequestBody(d); diags.HasError() { - return diags - } + if diags = category.CreateRequestBody(d); diags.HasError() { + return diags + } - req := newRequestConfig( - pc, - "POST", - fmt.Sprintf("/v1/config/categories/%s", categoryType), - category, - ) + req := newRequestConfig( + pc, + "POST", + fmt.Sprintf("/v1/config/categories/%s", categoryType), + category, + ) - body, err := req.MakeRequest() - log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - createdCategory := categoryResponse{} - err = json.Unmarshal(body, &createdCategory) + createdCategory := categoryResponse{} + err = json.Unmarshal(body, &createdCategory) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - log.Printf("[DEBUG] After %s categories, the created category is %+v", req.method, createdCategory) + log.Printf("[DEBUG] After %s categories, the created category is %+v", req.method, createdCategory) - // NOTE Type is added as a part of category ID to support import of categories - // Because type is required field even for read operation - d.SetId(fmt.Sprintf("%s:%s", createdCategory.Type, createdCategory.Id)) + // NOTE Type is added as a part of category ID to support import of categories + // Because type is required field even for read operation + d.SetId(fmt.Sprintf("%s:%s", createdCategory.Type, createdCategory.Id)) - return resourceCategoryRead(ctx, d, m) + return resourceCategoryRead(ctx, d, m) } func resourceCategoryUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - pc := m.(*providerConfig) + var diags diag.Diagnostics + pc := m.(*providerConfig) - categoryType, categoryId, err := parseCategoryId(d.Id()) + categoryType, categoryId, err := parseCategoryId(d.Id()) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - category := categoryRequest{} + category := categoryRequest{} - if diags = category.CreateRequestBody(d); diags.HasError() { - return diags - } + if diags = category.CreateRequestBody(d); diags.HasError() { + return diags + } - req := newRequestConfig( - pc, - "PUT", - fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), - category, - ) + req := newRequestConfig( + pc, + "PUT", + fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), + category, + ) - body, err := req.MakeRequest() - log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - log.Printf("[DEBUG] %s %s SUCCESS. Remote resource updated.", req.method, req.apiURL) + log.Printf("[DEBUG] %s %s SUCCESS. Remote resource updated.", req.method, req.apiURL) - return resourceCategoryRead(ctx, d, m) + return resourceCategoryRead(ctx, d, m) } func resourceCategoryRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - pc := m.(*providerConfig) - - categoryType, categoryId, err := parseCategoryId(d.Id()) - - if err != nil { - return diag.FromErr(err) - } - - req := newRequestConfig( - pc, - "GET", - fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), - nil, - ) - - body, err := req.MakeRequest() - - log.Printf("[DEBUG] GET categories raw response body %s\n", body) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Cannot read the remote categories resource", - Detail: err.Error(), - }) - return diags - } - - category := categoryResponse{} - err = json.Unmarshal(body, &category) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Cannot unmarshal response from the remote categories resource", - Detail: err.Error(), - }) - return diags - } - log.Printf("[DEBUG] The GET categories structure is as follows: %+v\n", category) - - appendError(d.Set("type", category.Type), &diags) - appendError(d.Set("name", category.Name), &diags) - - return diags + var diags diag.Diagnostics + + pc := m.(*providerConfig) + + categoryType, categoryId, err := parseCategoryId(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + req := newRequestConfig( + pc, + "GET", + fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), + nil, + ) + + body, err := req.MakeRequest() + + log.Printf("[DEBUG] GET categories raw response body %s\n", body) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot read the remote categories resource", + Detail: err.Error(), + }) + return diags + } + + category := categoryResponse{} + err = json.Unmarshal(body, &category) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot unmarshal response from the remote categories resource", + Detail: err.Error(), + }) + return diags + } + log.Printf("[DEBUG] The GET categories structure is as follows: %+v\n", category) + + appendError(d.Set("type", category.Type), &diags) + appendError(d.Set("name", category.Name), &diags) + + return diags } func resourceCategoryDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - pc := m.(*providerConfig) + pc := m.(*providerConfig) - categoryType, categoryId, err := parseCategoryId(d.Id()) + categoryType, categoryId, err := parseCategoryId(d.Id()) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - req := newRequestConfig( - pc, - "DELETE", - fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), - nil, - ) + req := newRequestConfig( + pc, + "DELETE", + fmt.Sprintf("/v1/config/categories/%s/%s", categoryType, categoryId), + nil, + ) - body, err := req.MakeRequest() - log.Printf("[DEBUG] %s %s presetalert %s", req.method, req.apiURL, body) + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s presetalert %s", req.method, req.apiURL, body) - if err != nil { - return diag.FromErr(err) - } - d.SetId("") - return nil + if err != nil { + return diag.FromErr(err) + } + d.SetId("") + return nil } func parseCategoryId(id string) (string, string, error) { - parts := strings.SplitN(id, ":", 2) + parts := strings.SplitN(id, ":", 2) - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return "", "", fmt.Errorf("Unexpected format of category ID (%s), expected Type:Id", id) - } + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("Unexpected format of category ID (%s), expected Type:Id", id) + } - return parts[0], parts[1], nil + return parts[0], parts[1], nil } func resourceCategory() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceCategoryCreate, - UpdateContext: resourceCategoryUpdate, - ReadContext: resourceCategoryRead, - DeleteContext: resourceCategoryDelete, - Importer: &schema.ResourceImporter{ - State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - categoryType, categoryId, err := parseCategoryId(d.Id()) - - if err != nil { - return nil, err - } - - if err := d.Set("type", categoryType); err != nil { - return nil, err - } - - d.SetId(fmt.Sprintf("%s:%s", categoryType, categoryId)) - - return []*schema.ResourceData{d}, nil - }, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - // NOTE Type is added to the schema but it's not used in a request body - // as the type is used just as a part of a url - "type": { - Type: schema.TypeString, - Optional: true, - Default: "views", - }, - }, - } + return &schema.Resource{ + CreateContext: resourceCategoryCreate, + UpdateContext: resourceCategoryUpdate, + ReadContext: resourceCategoryRead, + DeleteContext: resourceCategoryDelete, + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + categoryType, categoryId, err := parseCategoryId(d.Id()) + + if err != nil { + return nil, err + } + + if err := d.Set("type", categoryType); err != nil { + return nil, err + } + + d.SetId(fmt.Sprintf("%s:%s", categoryType, categoryId)) + + return []*schema.ResourceData{d}, nil + }, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + // NOTE Type is added to the schema but it's not used in a request body + // as the type is used just as a part of a url + "type": { + Type: schema.TypeString, + Optional: true, + Default: "views", + }, + }, + } } diff --git a/logdna/resource_category_test.go b/logdna/resource_category_test.go index eaeb8c9..0d822bc 100644 --- a/logdna/resource_category_test.go +++ b/logdna/resource_category_test.go @@ -1,99 +1,117 @@ package logdna import ( - "regexp" - "testing" - "strings" + "regexp" + "strings" + "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestCategory_ErrorProviderUrl(t *testing.T) { - pcArgs := []string{serviceKey, "https://api.logdna.co"} - catArgs := map[string]string{ - "name": `"test-category"`, - "type": `"views"`, - } + pcArgs := []string{serviceKey, "https://api.logdna.co"} + catArgs := map[string]string{ + "name": `"test-category"`, + "type": `"views"`, + } - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("category", "new", pcArgs, catArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("Error: error during HTTP request: Post \"https://api.logdna.co/v1/config/categories/views\": dial tcp: lookup api.logdna.co"), - }, - }, - }) + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("category", "new", pcArgs, catArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: error during HTTP request: Post \"https://api.logdna.co/v1/config/categories/views\": dial tcp: lookup api.logdna.co"), + }, + }, + }) +} + +func TestCategory_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + catArgs := map[string]string{ + "name": `"test-category"`, + "type": `"views"`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("category", "new", pcArgs, catArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_category\" resource"), + }, + }, + }) } func TestCategory_ErrorResourceName(t *testing.T) { - catArgs := map[string]string{ - "type": `"views"`, - } + catArgs := map[string]string{ + "type": `"views"`, + } - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("category", "new", globalPcArgs, catArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("The argument \"name\" is required, but no definition was found."), - }, - }, - }) + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("category", "new", globalPcArgs, catArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("The argument \"name\" is required, but no definition was found."), + }, + }, + }) } func TestCategory_ErrorResourceType(t *testing.T) { - catArgs := map[string]string{ - "name": `"test-category"`, - "type": `"incorrect"`, - } + catArgs := map[string]string{ + "name": `"test-category"`, + "type": `"incorrect"`, + } - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("category", "new", globalPcArgs, catArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("Error: POST .+?, status 400 NOT OK!"), - }, - }, - }) + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("category", "new", globalPcArgs, catArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: POST .+?, status 400 NOT OK!"), + }, + }, + }) } func TestCategory_Basic(t *testing.T) { - catInsArgs := map[string]string{ - "name": `"test-category"`, - "type": `"views"`, - } + catInsArgs := map[string]string{ + "name": `"test-category"`, + "type": `"views"`, + } - catUpdArgs := map[string]string{ - "name": `"test-category-updated"`, - "type": `"views"`, - } + catUpdArgs := map[string]string{ + "name": `"test-category-updated"`, + "type": `"views"`, + } - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - // NOTE It tests a category create operation - Config: fmtTestConfigResource("category", "new-category", globalPcArgs, catInsArgs, nilOpt, nilLst), - Check: resource.ComposeTestCheckFunc( - testResourceExists("category", "new-category"), - resource.TestCheckResourceAttr("logdna_category.new-category", "name", strings.Replace(catInsArgs["name"], "\"", "", 2)), - ), - }, - { - // NOTE It tests a category update operation - Config: fmtTestConfigResource("category", "new-category", globalPcArgs, catUpdArgs, nilOpt, nilLst), - Check: resource.ComposeTestCheckFunc( - testResourceExists("category", "new-category"), - resource.TestCheckResourceAttr("logdna_category.new-category", "name", strings.Replace(catUpdArgs["name"], "\"", "", 2)), - ), - }, - { - ResourceName: "logdna_category.new-category", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // NOTE It tests a category create operation + Config: fmtTestConfigResource("category", "new-category", globalPcArgs, catInsArgs, nilOpt, nilLst), + Check: resource.ComposeTestCheckFunc( + testResourceExists("category", "new-category"), + resource.TestCheckResourceAttr("logdna_category.new-category", "name", strings.Replace(catInsArgs["name"], "\"", "", 2)), + ), + }, + { + // NOTE It tests a category update operation + Config: fmtTestConfigResource("category", "new-category", globalPcArgs, catUpdArgs, nilOpt, nilLst), + Check: resource.ComposeTestCheckFunc( + testResourceExists("category", "new-category"), + resource.TestCheckResourceAttr("logdna_category.new-category", "name", strings.Replace(catUpdArgs["name"], "\"", "", 2)), + ), + }, + { + ResourceName: "logdna_category.new-category", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } diff --git a/logdna/resource_child_organization.go b/logdna/resource_child_organization.go new file mode 100644 index 0000000..b7a642c --- /dev/null +++ b/logdna/resource_child_organization.go @@ -0,0 +1,188 @@ +package logdna + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var _ = registerTerraform(TerraformInfo{ + name: "logdna_child_organization", + orgType: OrgTypeEnterprise, + terraformType: TerraformTypeResource, + schema: resourceChildOrg(), +}) + +func resourceChildOrgCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + pc := m.(*providerConfig) + + req := newRequestConfig( + pc, + "POST", + "/v1/enterprise/account", + nil, + ) + req.serviceKey = d.Get("servicekey").(string) + + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) + + if err != nil { + return diag.FromErr(err) + } + + createdChildOrg := childOrgResponse{} + err = json.Unmarshal(body, &createdChildOrg) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[DEBUG] After %s method, the created child org is %+v", req.method, createdChildOrg) + + d.SetId(createdChildOrg.Account) + return resourceChildOrgRead(ctx, d, m) +} + +func resourceChildOrgRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + pc := m.(*providerConfig) + childOrgID := d.Id() + + req := newRequestConfig( + pc, + "GET", + fmt.Sprintf("/v1/enterprise/account/%s", childOrgID), + nil, + ) + + body, err := req.MakeRequest() + log.Printf("[DEBUG] Making request with enterprisekey:%s, servicekey:%s\n", req.enterpriseKey, req.serviceKey) + + log.Printf("[DEBUG] GET child org raw response body %s\n", body) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot read the remote child org resource", + Detail: err.Error(), + }) + return diags + } + + childOrg := childOrgResponse{} + err = json.Unmarshal(body, &childOrg) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot unmarshal response from the remote childOrg resource", + Detail: err.Error(), + }) + return diags + } + log.Printf("[DEBUG] The GET child org structure is as follows: %+v\n", childOrg) + + // Top level keys can be set directly + appendError(d.Set("retention", childOrg.Retention), &diags) + appendError(d.Set("retention_tiers", childOrg.RetentionTiers), &diags) + + return diags +} + +func resourceChildOrgUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + pc := m.(*providerConfig) + childOrgID := d.Id() + + childOrg := childOrgPutRequest{} + if diags = childOrg.CreateRequestBody(d); diags.HasError() { + return diags + } + + req := newRequestConfig( + pc, + "PUT", + fmt.Sprintf("/v1/enterprise/account/%s", childOrgID), + childOrg, + ) + + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) + + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] %s %s SUCCESS. Remote resource updated.", req.method, req.apiURL) + + return resourceChildOrgRead(ctx, d, m) +} + +func resourceChildOrgDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + pc := m.(*providerConfig) + childOrgID := d.Id() + + childOrg := childOrgDeleteRequest{} + if diags = childOrg.CreateRequestBody(d); diags.HasError() { + return diags + } + + req := newRequestConfig( + pc, + "DELETE", + fmt.Sprintf("/v1/enterprise/account/%s", childOrgID), + childOrg, + ) + + body, err := req.MakeRequest() + log.Printf("[DEBUG] DELETE request body : %s", body) + + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} + +func resourceChildOrg() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceChildOrgCreate, + ReadContext: resourceChildOrgRead, + UpdateContext: resourceChildOrgUpdate, + DeleteContext: resourceChildOrgDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + "retention": { + Type: schema.TypeInt, + Optional: true, + }, + "retention_tiers": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Computed: true, + }, + "servicekey": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + Sensitive: true, + DiffSuppressFunc: func(_, _, _ string, _ *schema.ResourceData) bool { + return false + }, + }, + }, + } +} diff --git a/logdna/resource_child_organization_test.go b/logdna/resource_child_organization_test.go new file mode 100644 index 0000000..108085a --- /dev/null +++ b/logdna/resource_child_organization_test.go @@ -0,0 +1,51 @@ +package logdna + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestChildOrg_ErrorOrgType(t *testing.T) { + pcArgs := []string{serviceKey, apiHostUrl} + orgArgs := map[string]string{ + "servicekey": fmt.Sprintf(`"%s"`, serviceKey), + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("child_organization", "new", pcArgs, orgArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only enterprise organizations can instantiate a \"logdna_child_organization\" resource"), + }, + }, + }) +} + +func TestChildOrg_Basic(t *testing.T) { + orgArgs := map[string]string{ + "servicekey": fmt.Sprintf(`"%s"`, serviceKey), + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // NOTE: This tests detach childOrg operation + Config: fmtTestConfigResource("child_organization", "delete", []string{enterpriseServiceKey, apiHostUrl, "enterprise"}, orgArgs, nilOpt, nilLst), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("logdna_child_organization.delete", "servicekey", strings.Replace(orgArgs["servicekey"], "\"", "", 2)), + ), + }, + { + ResourceName: "logdna_child_organization.delete", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/logdna/resource_index_rate_alert.go b/logdna/resource_index_rate_alert.go index 614d842..48748ca 100644 --- a/logdna/resource_index_rate_alert.go +++ b/logdna/resource_index_rate_alert.go @@ -1,17 +1,24 @@ package logdna import ( - "context" - "encoding/json" - "log" + "context" + "encoding/json" + "log" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) const indexRateAlertConfigID = "config" +var _ = registerTerraform(TerraformInfo{ + name: "logdna_index_rate_alert", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceIndexRateAlert(), +}) + /** * Create/Update index rate alert resource * As API does not allow the POST method, this method calls PUT to be used for both create and update. @@ -19,100 +26,100 @@ const indexRateAlertConfigID = "config" * Only one config per account is allowed */ func resourceIndexRateAlertUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - pc := m.(*providerConfig) + var diags diag.Diagnostics + pc := m.(*providerConfig) - indexRateAlert := indexRateAlertRequest{} + indexRateAlert := indexRateAlertRequest{} - if diags = indexRateAlert.CreateRequestBody(d); diags.HasError() { - return diags - } + if diags = indexRateAlert.CreateRequestBody(d); diags.HasError() { + return diags + } - req := newRequestConfig( - pc, - "PUT", - "/v1/config/index-rate", - indexRateAlert, - ) + req := newRequestConfig( + pc, + "PUT", + "/v1/config/index-rate", + indexRateAlert, + ) - body, err := req.MakeRequest() - log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - createdIndexRateAlert := indexRateAlertResponse{} - err = json.Unmarshal(body, &createdIndexRateAlert) + createdIndexRateAlert := indexRateAlertResponse{} + err = json.Unmarshal(body, &createdIndexRateAlert) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - log.Printf("[DEBUG] %s %s SUCCESS. Remote resource updated.", req.method, req.apiURL) + log.Printf("[DEBUG] %s %s SUCCESS. Remote resource updated.", req.method, req.apiURL) - d.SetId(indexRateAlertConfigID) + d.SetId(indexRateAlertConfigID) - return resourceIndexRateAlertRead(ctx, d, m) + return resourceIndexRateAlertRead(ctx, d, m) } func resourceIndexRateAlertRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - pc := m.(*providerConfig) - - req := newRequestConfig( - pc, - "GET", - "/v1/config/index-rate", - nil, - ) - - body, err := req.MakeRequest() - - log.Printf("[DEBUG] GET IndexRateAlert raw response body %s\n", body) - - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Cannot read the remote IndexRateAlert resource", - Detail: err.Error(), - }) - return diags - } - - indexRateAlert := indexRateAlertResponse{} - - err = json.Unmarshal(body, &indexRateAlert) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Cannot unmarshal response from the remote indexRateAlert resource", - Detail: err.Error(), - }) - return diags - } - log.Printf("[DEBUG] The GET indexRateAlert structure is as follows: %+v\n", indexRateAlert) - - var channels []interface{} - - integrations := make(map[string]interface{}) - - integrations["email"] = indexRateAlert.Channels.Email - integrations["pagerduty"] = indexRateAlert.Channels.Pagerduty - integrations["slack"] = indexRateAlert.Channels.Slack - - channels = append(channels, integrations) - - appendError(d.Set("max_lines", indexRateAlert.MaxLines), &diags) - appendError(d.Set("max_z_score", indexRateAlert.MaxZScore), &diags) - appendError(d.Set("threshold_alert", indexRateAlert.ThresholdAlert), &diags) - appendError(d.Set("frequency", indexRateAlert.Frequency), &diags) - appendError(d.Set("channels", channels), &diags) - appendError(d.Set("enabled", indexRateAlert.Enabled), &diags) - - d.SetId(indexRateAlertConfigID) - - return diags + var diags diag.Diagnostics + pc := m.(*providerConfig) + + req := newRequestConfig( + pc, + "GET", + "/v1/config/index-rate", + nil, + ) + + body, err := req.MakeRequest() + + log.Printf("[DEBUG] GET IndexRateAlert raw response body %s\n", body) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot read the remote IndexRateAlert resource", + Detail: err.Error(), + }) + return diags + } + + indexRateAlert := indexRateAlertResponse{} + + err = json.Unmarshal(body, &indexRateAlert) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Cannot unmarshal response from the remote indexRateAlert resource", + Detail: err.Error(), + }) + return diags + } + log.Printf("[DEBUG] The GET indexRateAlert structure is as follows: %+v\n", indexRateAlert) + + var channels []interface{} + + integrations := make(map[string]interface{}) + + integrations["email"] = indexRateAlert.Channels.Email + integrations["pagerduty"] = indexRateAlert.Channels.Pagerduty + integrations["slack"] = indexRateAlert.Channels.Slack + + channels = append(channels, integrations) + + appendError(d.Set("max_lines", indexRateAlert.MaxLines), &diags) + appendError(d.Set("max_z_score", indexRateAlert.MaxZScore), &diags) + appendError(d.Set("threshold_alert", indexRateAlert.ThresholdAlert), &diags) + appendError(d.Set("frequency", indexRateAlert.Frequency), &diags) + appendError(d.Set("channels", channels), &diags) + appendError(d.Set("enabled", indexRateAlert.Enabled), &diags) + + d.SetId(indexRateAlertConfigID) + + return diags } /** @@ -121,103 +128,103 @@ func resourceIndexRateAlertRead(ctx context.Context, d *schema.ResourceData, m i * We considering delete action as just disabling a config */ func resourceIndexRateAlertDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - pc := m.(*providerConfig) + var diags diag.Diagnostics + pc := m.(*providerConfig) - resourceIndexRateAlertRead(ctx, d, m) + resourceIndexRateAlertRead(ctx, d, m) - indexRateAlert := indexRateAlertRequest{} + indexRateAlert := indexRateAlertRequest{} - if diags = indexRateAlert.CreateRequestBody(d); diags.HasError() { - return diags - } + if diags = indexRateAlert.CreateRequestBody(d); diags.HasError() { + return diags + } - indexRateAlert.Enabled = false + indexRateAlert.Enabled = false - req := newRequestConfig( - pc, - "PUT", - "/v1/config/index-rate", - indexRateAlert, - ) + req := newRequestConfig( + pc, + "PUT", + "/v1/config/index-rate", + indexRateAlert, + ) - body, err := req.MakeRequest() - log.Printf("[DEBUG] %s %s disable IndexRateAlert %s", req.method, req.apiURL, body) + body, err := req.MakeRequest() + log.Printf("[DEBUG] %s %s disable IndexRateAlert %s", req.method, req.apiURL, body) - if err != nil { - return diag.FromErr(err) - } + if err != nil { + return diag.FromErr(err) + } - d.SetId("") + d.SetId("") - return nil + return nil } func resourceIndexRateAlert() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceIndexRateAlertUpdate, - UpdateContext: resourceIndexRateAlertUpdate, - ReadContext: resourceIndexRateAlertRead, - DeleteContext: resourceIndexRateAlertDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "max_lines": { - Type: schema.TypeInt, - Optional: true, - Description: "Max number of lines for alert", - }, - "max_z_score": { - Type: schema.TypeInt, - Optional: true, - Description: "Max Z score before alerting", - }, - "threshold_alert": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"separate", "both"}, false), - }, - "frequency": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"hourly", "daily"}, false), - }, - "channels": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "email": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "pagerduty": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "slack": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "enabled": { - Type: schema.TypeBool, - Required: true, - }, - }, - } + return &schema.Resource{ + CreateContext: resourceIndexRateAlertUpdate, + UpdateContext: resourceIndexRateAlertUpdate, + ReadContext: resourceIndexRateAlertRead, + DeleteContext: resourceIndexRateAlertDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "max_lines": { + Type: schema.TypeInt, + Optional: true, + Description: "Max number of lines for alert", + }, + "max_z_score": { + Type: schema.TypeInt, + Optional: true, + Description: "Max Z score before alerting", + }, + "threshold_alert": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"separate", "both"}, false), + }, + "frequency": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"hourly", "daily"}, false), + }, + "channels": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "email": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "pagerduty": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "slack": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + }, + } } diff --git a/logdna/resource_index_rate_alert_test.go b/logdna/resource_index_rate_alert_test.go index 69bc47a..de87b02 100644 --- a/logdna/resource_index_rate_alert_test.go +++ b/logdna/resource_index_rate_alert_test.go @@ -1,364 +1,393 @@ package logdna import ( - "regexp" - "testing" - "strings" - "strconv" + "regexp" + "strconv" + "strings" + "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) var escapeChar = regexp.MustCompile(`\"|\[|\]|,`) func TestIndexRateAlert_ErrorProviderUrl(t *testing.T) { - pcArgs := []string{serviceKey, "https://api.logdna.co"} - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "frequency": `"hourly"`, - "enabled": `false`, - } - - chArgs := map[string]map[string]string{ - "channels": { - "email": `["test@logdna.com", "test2@logdna.com"]`, - }, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", pcArgs, iraArgs, chArgs, nilLst), - ExpectError: regexp.MustCompile("Error: error during HTTP request: Put \"https://api.logdna.co/v1/config/index-rate\": dial tcp: lookup api.logdna.co.+?"), - }, - }, - }) + pcArgs := []string{serviceKey, "https://api.logdna.co"} + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + chArgs := map[string]map[string]string{ + "channels": { + "email": `["test@logdna.com", "test2@logdna.com"]`, + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", pcArgs, iraArgs, chArgs, nilLst), + ExpectError: regexp.MustCompile("Error: error during HTTP request: Put \"https://api.logdna.co/v1/config/index-rate\": dial tcp: lookup api.logdna.co.+?"), + }, + }, + }) +} + +func TestIndexRateAlert_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + chArgs := map[string]map[string]string{ + "channels": { + "email": `["test@logdna.com", "test2@logdna.com"]`, + "slack": `["https://hooks.slack.com/KEY"]`, + "pagerduty": `["ndt3k75rsw520d8t55dv35decdyt3mkcb3r"]`, + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "new", pcArgs, iraArgs, chArgs, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_index_rate_alert\" resource"), + }, + }, + }) } func TestIndexRateAlert_ErrorResourceThresholdAlertInvalid(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"invalid"`, - "frequency": `"hourly"`, - "enabled": `false`, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("Error: expected threshold_alert to be one of .+?"), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"invalid"`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: expected threshold_alert to be one of .+?"), + }, + }, + }) } func TestIndexRateAlert_ErrorResourceThresholdAlertMissed(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "frequency": `"hourly"`, - "enabled": `false`, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("The argument \"threshold_alert\" is required, but no definition was found."), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("The argument \"threshold_alert\" is required, but no definition was found."), + }, + }, + }) } func TestIndexRateAlert_ErrorResourceFrequencyInvalid(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "frequency": `"ivalid"`, - "enabled": `false`, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("Error: expected frequency to be one of .+?"), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"ivalid"`, + "enabled": `false`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: expected frequency to be one of .+?"), + }, + }, + }) } func TestIndexRateAlert_ErrorResourceFrequencyMissed(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "enabled": `false`, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("The argument \"frequency\" is required, but no definition was found."), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "enabled": `false`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("The argument \"frequency\" is required, but no definition was found."), + }, + }, + }) } func TestIndexRateAlert_ErrorResourceEnable(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "frequency": `"hourly"`, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), - ExpectError: regexp.MustCompile("The argument \"enabled\" is required, but no definition was found."), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("The argument \"enabled\" is required, but no definition was found."), + }, + }, + }) } func TestIndexRateAlert_ErrorChannels(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "frequency": `"hourly"`, - "enabled": `false`, - } - - chArgs := map[string]map[string]string{ - "channels": { - "email": `["test@logdna.com"]`, - }, - "channels1": { - "email": `["test2@logdna.com"]`, - }, - } - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, chArgs, nilLst), - ExpectError: regexp.MustCompile("Index rate alert resource supports only one channels object"), - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + chArgs := map[string]map[string]string{ + "channels": { + "email": `["test@logdna.com"]`, + }, + "channels1": { + "email": `["test2@logdna.com"]`, + }, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, chArgs, nilLst), + ExpectError: regexp.MustCompile("Index rate alert resource supports only one channels object"), + }, + }, + }) } func TestIndexRateAlert_Basic(t *testing.T) { - iraArgs := map[string]string{ - "max_lines": `3`, - "max_z_score": `3`, - "threshold_alert": `"separate"`, - "frequency": `"hourly"`, - "enabled": `false`, - } - - chArgs := map[string]map[string]string{ - "channels": { - "email": `["test@logdna.com", "test2@logdna.com"]`, - "slack": `["https://hooks.slack.com/KEY"]`, - "pagerduty": `["ndt3k75rsw520d8t55dv35decdyt3mkcb3r"]`, - }, - } - - iraUpdArgs := map[string]string{ - "max_lines": `5`, - "max_z_score": `5`, - "threshold_alert": `"separate"`, - "frequency": `"hourly"`, - "enabled": `true`, - } - - chUpdArgs := map[string]map[string]string{ - "channels": { - "email": `["test_updated@logdna.com"]`, - "slack": `["https://hooks.slack.com/UPDATED_KEY", "https://hooks.slack.com/KEY_2"]`, - "pagerduty": `["new3k75rsw520d8t55dv35decdyt3mkcnew"]`, - }, - } - - createdEmails := strings.Split( - escapeChar.ReplaceAllString(chArgs["channels"]["email"], ""), - " ", - ) - - createdSlack := strings.Split( - escapeChar.ReplaceAllString(chArgs["channels"]["slack"], ""), - " ", - ) - - createdPagerduty := strings.Split( - escapeChar.ReplaceAllString(chArgs["channels"]["pagerduty"], ""), - " ", - ) - - updatedEmails := strings.Split( - escapeChar.ReplaceAllString(chUpdArgs["channels"]["email"], ""), - " ", - ) - - updatedSlack := strings.Split( - escapeChar.ReplaceAllString(chUpdArgs["channels"]["slack"], ""), - " ", - ) - - updatedPagerduty := strings.Split( - escapeChar.ReplaceAllString(chUpdArgs["channels"]["pagerduty"], ""), - " ", - ) - - resource.Test(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - // NOTE It tests a index rate alert create operation - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, chArgs, nilLst), - Check: resource.ComposeTestCheckFunc( - testResourceExists("index_rate_alert", "test_config"), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "max_lines", - escapeChar.ReplaceAllString(iraArgs["max_lines"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "max_z_score", - escapeChar.ReplaceAllString(iraArgs["max_z_score"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "threshold_alert", - escapeChar.ReplaceAllString(iraArgs["threshold_alert"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "frequency", - escapeChar.ReplaceAllString(iraArgs["frequency"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.email.#", - strconv.Itoa(len(createdEmails)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.email.0", - createdEmails[0], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.email.1", - createdEmails[1], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.slack.#", - strconv.Itoa(len(createdSlack)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.slack.0", - createdSlack[0], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.pagerduty.#", - strconv.Itoa(len(createdPagerduty)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.pagerduty.0", - createdPagerduty[0], - ), - ), - }, - { - // NOTE It tests a index rate alert config update operation - Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraUpdArgs, chUpdArgs, nilLst), - Check: resource.ComposeTestCheckFunc( - testResourceExists("index_rate_alert", "test_config"), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "max_lines", - escapeChar.ReplaceAllString(iraUpdArgs["max_lines"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "max_z_score", - escapeChar.ReplaceAllString(iraUpdArgs["max_z_score"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "threshold_alert", - escapeChar.ReplaceAllString(iraUpdArgs["threshold_alert"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "frequency", - escapeChar.ReplaceAllString(iraUpdArgs["frequency"], ""), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.email.#", - strconv.Itoa(len(updatedEmails)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.email.0", - updatedEmails[0], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.slack.#", - strconv.Itoa(len(updatedSlack)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.slack.0", - updatedSlack[0], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.slack.1", - updatedSlack[1], - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.pagerduty.#", - strconv.Itoa(len(updatedPagerduty)), - ), - resource.TestCheckResourceAttr( - "logdna_index_rate_alert.test_config", - "channels.0.pagerduty.0", - updatedPagerduty[0], - ), - ), - }, - { - ResourceName: "logdna_index_rate_alert.test_config", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + iraArgs := map[string]string{ + "max_lines": `3`, + "max_z_score": `3`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + "enabled": `false`, + } + + chArgs := map[string]map[string]string{ + "channels": { + "email": `["test@logdna.com", "test2@logdna.com"]`, + "slack": `["https://hooks.slack.com/KEY"]`, + "pagerduty": `["ndt3k75rsw520d8t55dv35decdyt3mkcb3r"]`, + }, + } + + iraUpdArgs := map[string]string{ + "max_lines": `5`, + "max_z_score": `5`, + "threshold_alert": `"separate"`, + "frequency": `"hourly"`, + "enabled": `true`, + } + + chUpdArgs := map[string]map[string]string{ + "channels": { + "email": `["test_updated@logdna.com"]`, + "slack": `["https://hooks.slack.com/UPDATED_KEY", "https://hooks.slack.com/KEY_2"]`, + "pagerduty": `["new3k75rsw520d8t55dv35decdyt3mkcnew"]`, + }, + } + + createdEmails := strings.Split( + escapeChar.ReplaceAllString(chArgs["channels"]["email"], ""), + " ", + ) + + createdSlack := strings.Split( + escapeChar.ReplaceAllString(chArgs["channels"]["slack"], ""), + " ", + ) + + createdPagerduty := strings.Split( + escapeChar.ReplaceAllString(chArgs["channels"]["pagerduty"], ""), + " ", + ) + + updatedEmails := strings.Split( + escapeChar.ReplaceAllString(chUpdArgs["channels"]["email"], ""), + " ", + ) + + updatedSlack := strings.Split( + escapeChar.ReplaceAllString(chUpdArgs["channels"]["slack"], ""), + " ", + ) + + updatedPagerduty := strings.Split( + escapeChar.ReplaceAllString(chUpdArgs["channels"]["pagerduty"], ""), + " ", + ) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // NOTE It tests a index rate alert create operation + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, chArgs, nilLst), + Check: resource.ComposeTestCheckFunc( + testResourceExists("index_rate_alert", "test_config"), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "max_lines", + escapeChar.ReplaceAllString(iraArgs["max_lines"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "max_z_score", + escapeChar.ReplaceAllString(iraArgs["max_z_score"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "threshold_alert", + escapeChar.ReplaceAllString(iraArgs["threshold_alert"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "frequency", + escapeChar.ReplaceAllString(iraArgs["frequency"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.email.#", + strconv.Itoa(len(createdEmails)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.email.0", + createdEmails[0], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.email.1", + createdEmails[1], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.slack.#", + strconv.Itoa(len(createdSlack)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.slack.0", + createdSlack[0], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.pagerduty.#", + strconv.Itoa(len(createdPagerduty)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.pagerduty.0", + createdPagerduty[0], + ), + ), + }, + { + // NOTE It tests a index rate alert config update operation + Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraUpdArgs, chUpdArgs, nilLst), + Check: resource.ComposeTestCheckFunc( + testResourceExists("index_rate_alert", "test_config"), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "max_lines", + escapeChar.ReplaceAllString(iraUpdArgs["max_lines"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "max_z_score", + escapeChar.ReplaceAllString(iraUpdArgs["max_z_score"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "threshold_alert", + escapeChar.ReplaceAllString(iraUpdArgs["threshold_alert"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "frequency", + escapeChar.ReplaceAllString(iraUpdArgs["frequency"], ""), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.email.#", + strconv.Itoa(len(updatedEmails)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.email.0", + updatedEmails[0], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.slack.#", + strconv.Itoa(len(updatedSlack)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.slack.0", + updatedSlack[0], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.slack.1", + updatedSlack[1], + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.pagerduty.#", + strconv.Itoa(len(updatedPagerduty)), + ), + resource.TestCheckResourceAttr( + "logdna_index_rate_alert.test_config", + "channels.0.pagerduty.0", + updatedPagerduty[0], + ), + ), + }, + { + ResourceName: "logdna_index_rate_alert.test_config", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } diff --git a/logdna/resource_ingestion_exclusion.go b/logdna/resource_ingestion_exclusion.go index 58e90af..20d2a9e 100644 --- a/logdna/resource_ingestion_exclusion.go +++ b/logdna/resource_ingestion_exclusion.go @@ -11,10 +11,17 @@ import ( const baseIngestionExclusionUrl = "/v1/config/ingestion/exclusions" +var _ = registerTerraform(TerraformInfo{ + name: "logdna_ingestion_exclusion", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceIngestionExclusion(), +}) + func resourceIngestionExclusionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - pc := m.(*providerConfig) + ex := exclusionRule{ Title: d.Get("title").(string), Active: d.Get("active").(bool), diff --git a/logdna/resource_key.go b/logdna/resource_key.go index bbd5088..c36fdcf 100644 --- a/logdna/resource_key.go +++ b/logdna/resource_key.go @@ -11,6 +11,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_key", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceKey(), +}) + func resourceKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics pc := m.(*providerConfig) diff --git a/logdna/resource_key_test.go b/logdna/resource_key_test.go index 8887a03..cbdd0ab 100644 --- a/logdna/resource_key_test.go +++ b/logdna/resource_key_test.go @@ -22,6 +22,24 @@ func TestKey_ErrorResourceTypeUndefined(t *testing.T) { }) } +func TestKey_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + keyArgs := map[string]string{ + "type": `"service"`, + "name": `"my first name"`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("key", "new", pcArgs, keyArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_key\" resource"), + }, + }, + }) +} + func TestKey_ErrorResourceTypeInvalid(t *testing.T) { args := map[string]string{ "type": `"incorrect"`, diff --git a/logdna/resource_member.go b/logdna/resource_member.go index 22967d0..6c2bbf3 100644 --- a/logdna/resource_member.go +++ b/logdna/resource_member.go @@ -12,12 +12,18 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_member", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceMember(), +}) + func resourceMemberCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics pc := m.(*providerConfig) member := memberRequest{} - if diags = member.CreateRequestBody(d); diags.HasError() { return diags } diff --git a/logdna/resource_member_test.go b/logdna/resource_member_test.go index 255d52f..b3ef113 100644 --- a/logdna/resource_member_test.go +++ b/logdna/resource_member_test.go @@ -24,6 +24,24 @@ func TestMember_ErrorRoleEmpty(t *testing.T) { }) } +func TestMember_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + memberArgs := map[string]string{ + "email": `"member@example.org"`, + "role": `"member"`, + } + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("member", "new", pcArgs, memberArgs, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_member\" resource"), + }, + }, + }) +} + func TestMember_Basic(t *testing.T) { memberArgs := map[string]string{ "email": `"member@example.org"`, diff --git a/logdna/resource_stream_config.go b/logdna/resource_stream_config.go index 5af44d2..256021d 100644 --- a/logdna/resource_stream_config.go +++ b/logdna/resource_stream_config.go @@ -10,6 +10,13 @@ import ( const streamConfigID = "stream" +var _ = registerTerraform(TerraformInfo{ + name: "logdna_stream_config", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceStreamConfig(), +}) + type streamConfig struct { Status string `json:"status,omitempty"` Brokers []string `json:"brokers"` diff --git a/logdna/resource_stream_exclusion.go b/logdna/resource_stream_exclusion.go index fdd72b6..10ae607 100644 --- a/logdna/resource_stream_exclusion.go +++ b/logdna/resource_stream_exclusion.go @@ -9,10 +9,17 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_stream_exclusion", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceStreamExclusion(), +}) + func resourceStreamExclusionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics - pc := m.(*providerConfig) + ex := exclusionRule{ Title: d.Get("title").(string), Active: d.Get("active").(bool), diff --git a/logdna/resource_view.go b/logdna/resource_view.go index 40d60bf..ae9dc37 100644 --- a/logdna/resource_view.go +++ b/logdna/resource_view.go @@ -20,6 +20,13 @@ const ( WEBHOOK = "webhook" ) +var _ = registerTerraform(TerraformInfo{ + name: "logdna_view", + orgType: OrgTypeRegular, + terraformType: TerraformTypeResource, + schema: resourceView(), +}) + func resourceViewCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics pc := m.(*providerConfig) @@ -106,7 +113,7 @@ func resourceViewRead(ctx context.Context, d *schema.ResourceData, m interface{} // NOTE API does DB denormalization and extend a view record in DB // with a alert channels which break a schema validation here. - // We don't need the channels field in case when a presetid exists + // We don't need the channels field in case when a presetid exists if len(d.Get("presetid").(string)) > 0 { return diags } @@ -225,8 +232,8 @@ func resourceView() *schema.Resource { Optional: true, }, "presetid": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, ConflictsWith: []string{ "email_channel", "pagerduty_channel", diff --git a/logdna/resource_view_test.go b/logdna/resource_view_test.go index 0b6e73f..4e7de64 100644 --- a/logdna/resource_view_test.go +++ b/logdna/resource_view_test.go @@ -26,6 +26,20 @@ func TestView_ErrorProviderUrl(t *testing.T) { }) } +func TestView_ErrorOrgType(t *testing.T) { + pcArgs := []string{enterpriseServiceKey, apiHostUrl, "enterprise"} + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: fmtTestConfigResource("view", "new", pcArgs, viewDefaults, nilOpt, nilLst), + ExpectError: regexp.MustCompile("Error: Only regular organizations can instantiate a \"logdna_view\" resource"), + }, + }, + }) +} + func TestView_ErrorsResourceFields(t *testing.T) { nme := cloneDefaults(rsDefaults["view"]) nme["name"] = "" diff --git a/logdna/response_types.go b/logdna/response_types.go index 34439f9..0aca8e5 100644 --- a/logdna/response_types.go +++ b/logdna/response_types.go @@ -45,6 +45,12 @@ type memberResponse struct { Groups []string `json:"groups,omitempty"` } +type childOrgResponse struct { + Account string `json:"account"` + Retention int `json:"retention"` + RetentionTiers []int `json:"retentionTiers"` +} + // channelResponse contains channel data returned from the logdna APIs // NOTE - Properties with `interface` are due to the APIs returning // some things as strings (PUT/emails) and other times arrays (GET/emails)