Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new "logdna_enterprise_key" resource #86

Open
wants to merge 2 commits into
base: jakedipity/LOG-13116
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/resources/logdna_enterprise_key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Resource: `logdna_enterprise_key`

This resource allows you to manage enterprise keys.

## Example

```hcl
provider "logdna" {
servicekey = "xxxxxxxxxxxxxxxxxxxxxxxx"
}

resource "logdna_enterprise_key" "enterprise_service_key" {
lifecycle {
create_before_destroy = true
}
}
```

The `create_before_destroy` and `lifecycle` meta-argument are not required; however, these options ensure a valid key is always available when a key is being recreated. This helps avoid any disruptions in service.

~> **NOTE:** We recommend prefixing the name of your terraform resources so they can be distinguished from other resources in the UI.

## Key Rotation

This resource can be used in conjuction with automated scripts to perform automatic key rotations, e.g.,

```sh
# Run this every time you want to rotate the key
$ terraform apply -replace="logdna_enterprise_key.my_key"
```

## Argument Reference

This resource does not support any input arguments.

## Attributes Reference

In addition to all the arguments above, the following attributes are exported:

- `id`: **string** The unique identifier of this key.
- `key`: **string** The actual key value.
- `name`: **string** The name of the key.
- `created`: **int** The date the key was created in Unix time milliseconds.

## Import

A key can be imported using the `id`, e.g.,

```sh
$ terraform import logdna_enterprise_key.my_key <id>
```
8 changes: 6 additions & 2 deletions logdna/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
7 changes: 7 additions & 0 deletions logdna/data_source_alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 16 additions & 0 deletions logdna/data_source_alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package logdna

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand All @@ -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"]),
Expand Down
89 changes: 89 additions & 0 deletions logdna/meta.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
43 changes: 25 additions & 18 deletions logdna/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -18,40 +20,45 @@ 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,
Optional: true,
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
Expand Down
1 change: 1 addition & 0 deletions logdna/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 testAccProviders map[string]*schema.Provider
Expand Down
52 changes: 35 additions & 17 deletions logdna/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
)

Expand All @@ -17,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: io.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
Expand All @@ -64,7 +75,14 @@ func (c *requestConfig) MakeRequest() ([]byte, error) {
if payloadBuf.Len() > 0 {
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)
Expand Down
Loading