Skip to content

Commit e5c0365

Browse files
committed
feat: add new "logdna_enterprise_key" resource
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]>
1 parent dd9048b commit e5c0365

File tree

3 files changed

+233
-0
lines changed

3 files changed

+233
-0
lines changed
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Resource: `logdna_enterprise_key`
2+
3+
This resource allows you to manage enterprise keys.
4+
5+
## Example
6+
7+
```hcl
8+
provider "logdna" {
9+
servicekey = "xxxxxxxxxxxxxxxxxxxxxxxx"
10+
}
11+
12+
resource "logdna_enterprise_key" "enterprise_service_key" {
13+
lifecycle {
14+
create_before_destroy = true
15+
}
16+
}
17+
```
18+
19+
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.
20+
21+
~> **NOTE:** We recommend prefixing the name of your terraform resources so they can be distinguished from other resources in the UI.
22+
23+
## Key Rotation
24+
25+
This resource can be used in conjuction with automated scripts to perform automatic key rotations, e.g.,
26+
27+
```sh
28+
# Run this every time you want to rotate the key
29+
$ terraform apply -replace="logdna_enterprise_key.my_key"
30+
```
31+
32+
## Argument Reference
33+
34+
This resource does not support any input arguments.
35+
36+
## Attributes Reference
37+
38+
In addition to all the arguments above, the following attributes are exported:
39+
40+
- `id`: **string** The unique identifier of this key.
41+
- `key`: **string** The actual key value.
42+
- `name`: **string** The name of the key.
43+
- `created`: **int** The date the key was created in Unix time milliseconds.
44+
45+
## Import
46+
47+
A key can be imported using the `id`, e.g.,
48+
49+
```sh
50+
$ terraform import logdna_enterprise_key.my_key <id>
51+
```

logdna/request_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,32 @@ func TestRequest_MakeRequest(t *testing.T) {
121121
assert.Nil(err, "No errors")
122122
})
123123

124+
t.Run("Server receives proper method, URL, and headers for enterprise org", func(t *testing.T) {
125+
enterprisePC := providerConfig{serviceKey: "abc123", orgType: OrgTypeEnterprise, httpClient: &http.Client{Timeout: 15 * time.Second}}
126+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127+
assert.Equal("GET", r.Method, "method is correct")
128+
assert.Equal(fmt.Sprintf("/someapi/%s", resourceID), r.URL.String(), "URL is correct")
129+
key, ok := r.Header["Enterprise-Servicekey"]
130+
assert.Equal(true, ok, "enterprise-servicekey header exists")
131+
assert.Equal(1, len(key), "enterprise-servicekey header is correct")
132+
key = r.Header["Content-Type"]
133+
assert.Equal("application/json", key[0], "content-type header is correct")
134+
}))
135+
defer ts.Close()
136+
137+
enterprisePC.baseURL = ts.URL
138+
139+
req := newRequestConfig(
140+
&enterprisePC,
141+
"GET",
142+
fmt.Sprintf("/someapi/%s", resourceID),
143+
nil,
144+
)
145+
146+
_, err := req.MakeRequest()
147+
assert.Nil(err, "No errors")
148+
})
149+
124150
t.Run("Reads and decodes response from the server", func(t *testing.T) {
125151
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126152
err := json.NewEncoder(w).Encode(viewResponse{ViewID: "test123456"})

logdna/resource_enterprise_key.go

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package logdna
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
)
12+
13+
var _ = registerTerraform(TerraformInfo{
14+
name: "logdna_enterprise_key",
15+
orgType: OrgTypeEnterprise,
16+
terraformType: TerraformTypeResource,
17+
schema: resourceEnterpriseKey(),
18+
})
19+
20+
func resourceEnterpriseKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
21+
var diags diag.Diagnostics
22+
pc := m.(*providerConfig)
23+
24+
key := keyRequest{}
25+
26+
if diags = key.CreateRequestBody(d); diags.HasError() {
27+
return diags
28+
}
29+
30+
req := newRequestConfig(
31+
pc,
32+
"POST",
33+
"/v1/enterprise/keys",
34+
nil,
35+
)
36+
37+
body, err := req.MakeRequest()
38+
log.Printf("[DEBUG] %s %s, payload is: %s", req.method, req.apiURL, body)
39+
40+
if err != nil {
41+
return diag.FromErr(err)
42+
}
43+
44+
createdKey := keyResponse{}
45+
err = json.Unmarshal(body, &createdKey)
46+
if err != nil {
47+
return diag.FromErr(err)
48+
}
49+
log.Printf("[DEBUG] After %s key, the created key is %+v", req.method, createdKey)
50+
51+
d.SetId(createdKey.KeyID)
52+
53+
return resourceEnterpriseKeyRead(ctx, d, m)
54+
}
55+
56+
func resourceEnterpriseKeyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
57+
var diags diag.Diagnostics
58+
59+
pc := m.(*providerConfig)
60+
keyID := d.Id()
61+
62+
req := newRequestConfig(
63+
pc,
64+
"GET",
65+
fmt.Sprintf("/v1/enterprise/keys/%s", keyID),
66+
nil,
67+
)
68+
69+
body, err := req.MakeRequest()
70+
71+
log.Printf("[DEBUG] GET key raw response body %s\n", body)
72+
if err != nil {
73+
diags = append(diags, diag.Diagnostic{
74+
Severity: diag.Error,
75+
Summary: "Cannot read the remote key resource",
76+
Detail: err.Error(),
77+
})
78+
return diags
79+
}
80+
81+
key := keyResponse{}
82+
err = json.Unmarshal(body, &key)
83+
if err != nil {
84+
diags = append(diags, diag.Diagnostic{
85+
Severity: diag.Error,
86+
Summary: "Cannot unmarshal response from the remote key resource",
87+
Detail: err.Error(),
88+
})
89+
return diags
90+
}
91+
log.Printf("[DEBUG] The GET key structure is as follows: %+v\n", key)
92+
93+
// Top level keys can be set directly
94+
appendError(d.Set("name", key.Name), &diags)
95+
appendError(d.Set("id", key.KeyID), &diags)
96+
appendError(d.Set("key", key.Key), &diags)
97+
appendError(d.Set("created", key.Created), &diags)
98+
99+
return diags
100+
}
101+
102+
func resourceEnterpriseKeyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
103+
pc := m.(*providerConfig)
104+
keyID := d.Id()
105+
106+
req := newRequestConfig(
107+
pc,
108+
"DELETE",
109+
fmt.Sprintf("/v1/enterprise/keys/%s", keyID),
110+
nil,
111+
)
112+
113+
body, err := req.MakeRequest()
114+
log.Printf("[DEBUG] %s %s key %s", req.method, req.apiURL, body)
115+
116+
if err != nil {
117+
return diag.FromErr(err)
118+
}
119+
120+
d.SetId("")
121+
return nil
122+
}
123+
124+
func resourceEnterpriseKey() *schema.Resource {
125+
return &schema.Resource{
126+
CreateContext: resourceEnterpriseKeyCreate,
127+
ReadContext: resourceEnterpriseKeyRead,
128+
DeleteContext: resourceEnterpriseKeyDelete,
129+
Importer: &schema.ResourceImporter{
130+
StateContext: schema.ImportStatePassthroughContext,
131+
},
132+
133+
Schema: map[string]*schema.Schema{
134+
"name": {
135+
Type: schema.TypeString,
136+
Computed: true,
137+
},
138+
"id": {
139+
Type: schema.TypeString,
140+
ForceNew: true,
141+
Computed: true,
142+
},
143+
"key": {
144+
Type: schema.TypeString,
145+
ForceNew: true,
146+
Sensitive: true,
147+
Computed: true,
148+
},
149+
"created": {
150+
Type: schema.TypeInt,
151+
ForceNew: true,
152+
Computed: true,
153+
},
154+
},
155+
}
156+
}

0 commit comments

Comments
 (0)