Skip to content

Commit

Permalink
feat: add new "logdna_enterprise_key" resource
Browse files Browse the repository at this point in the history
This new resource allows creation and management of enterprise-level
keys. This resource is separate from "logdna_key" which controls the
management of non-enterpise accounts.

Ref: LOG-15052
Signed-off-by: Jacob Hull <[email protected]>
  • Loading branch information
jakedipity committed Jan 17, 2023
1 parent dd9048b commit e5c0365
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 0 deletions.
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>
```
26 changes: 26 additions & 0 deletions logdna/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,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"})
Expand Down
156 changes: 156 additions & 0 deletions logdna/resource_enterprise_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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_enterprise_key",
orgType: OrgTypeEnterprise,
terraformType: TerraformTypeResource,
schema: resourceEnterpriseKey(),
})

func resourceEnterpriseKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
pc := m.(*providerConfig)

key := keyRequest{}

if diags = key.CreateRequestBody(d); diags.HasError() {
return diags
}

req := newRequestConfig(
pc,
"POST",
"/v1/enterprise/keys",
nil,
)

body, err := req.MakeRequest()
log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body)

if err != nil {
return diag.FromErr(err)
}

createdKey := keyResponse{}
err = json.Unmarshal(body, &createdKey)
if err != nil {
return diag.FromErr(err)
}
log.Printf("[DEBUG] After %s key, the created key is %+v", req.method, createdKey)

d.SetId(createdKey.KeyID)

return resourceEnterpriseKeyRead(ctx, d, m)
}

func resourceEnterpriseKeyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

pc := m.(*providerConfig)
keyID := d.Id()

req := newRequestConfig(
pc,
"GET",
fmt.Sprintf("/v1/enterprise/keys/%s", keyID),
nil,
)

body, err := req.MakeRequest()

log.Printf("[DEBUG] GET key raw response body %s\n", body)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Cannot read the remote key resource",
Detail: err.Error(),
})
return diags
}

key := keyResponse{}
err = json.Unmarshal(body, &key)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Cannot unmarshal response from the remote key resource",
Detail: err.Error(),
})
return diags
}
log.Printf("[DEBUG] The GET key structure is as follows: %+v\n", key)

// Top level keys can be set directly
appendError(d.Set("name", key.Name), &diags)
appendError(d.Set("id", key.KeyID), &diags)
appendError(d.Set("key", key.Key), &diags)
appendError(d.Set("created", key.Created), &diags)

return diags
}

func resourceEnterpriseKeyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
pc := m.(*providerConfig)
keyID := d.Id()

req := newRequestConfig(
pc,
"DELETE",
fmt.Sprintf("/v1/enterprise/keys/%s", keyID),
nil,
)

body, err := req.MakeRequest()
log.Printf("[DEBUG] %s %s key %s", req.method, req.apiURL, body)

if err != nil {
return diag.FromErr(err)
}

d.SetId("")
return nil
}

func resourceEnterpriseKey() *schema.Resource {
return &schema.Resource{
CreateContext: resourceEnterpriseKeyCreate,
ReadContext: resourceEnterpriseKeyRead,
DeleteContext: resourceEnterpriseKeyDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Computed: true,
},
"id": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
},
"key": {
Type: schema.TypeString,
ForceNew: true,
Sensitive: true,
Computed: true,
},
"created": {
Type: schema.TypeInt,
ForceNew: true,
Computed: true,
},
},
}
}

0 comments on commit e5c0365

Please sign in to comment.