Skip to content

Commit

Permalink
Add vault_quota_rate_limit resource (hashicorp#825)
Browse files Browse the repository at this point in the history
* Add `vault_quota_rate_limit` resource

* Fix tab

* Fix documentation

* Remove unused functionemove unused function

* Add validation function

* Make `path` `ForceNew`
  • Loading branch information
lawliet89 authored Sep 10, 2020
1 parent d16aa3e commit 7c7ece4
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 0 deletions.
4 changes: 4 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,10 @@ var (
Resource: pkiSecretBackendSignResource(),
PathInventory: []string{"/pki/sign/{role}"},
},
"vault_quota_rate_limit": {
Resource: quotaRateLimitResource(),
PathInventory: []string{"/sys/quotas/rate-limit/{name}"},
},
"vault_transit_secret_backend_key": {
Resource: transitSecretBackendKeyResource(),
PathInventory: []string{"/transit/keys/{name}"},
Expand Down
156 changes: 156 additions & 0 deletions vault/resource_quota_rate_limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package vault

import (
"fmt"
"log"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/hashicorp/vault/api"
)

func quotaRateLimitPath(name string) string {
return "sys/quotas/rate-limit/" + name
}

func quotaRateLimitResource() *schema.Resource {
return &schema.Resource{
Create: quotaRateLimitCreate,
Read: quotaRateLimitRead,
Update: quotaRateLimitUpdate,
Delete: quotaRateLimitDelete,
Exists: quotaRateLimitExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the quota.",
ForceNew: true,
},
"path": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "Path of the mount or namespace to apply the quota. A blank path configures a global rate limit quota.",
},
"rate": {
Type: schema.TypeFloat,
Required: true,
Description: "The maximum number of requests at any given second to be allowed by the quota rule. The rate must be positive.",
ValidateFunc: validation.FloatAtLeast(0.0),
},
},
}
}

func quotaRateLimitCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

name := d.Get("name").(string)
path := quotaRateLimitPath(name)
d.SetId(name)

log.Printf("[DEBUG] Creating Resource Rate Limit Quota %s", name)

data := map[string]interface{}{}
data["path"] = d.Get("path").(string)
data["rate"] = d.Get("rate").(float64)

_, err := client.Logical().Write(path, data)
if err != nil {
d.SetId("")
return fmt.Errorf("Error creating Resource Rate Limit Quota %s: %s", name, err)
}
log.Printf("[DEBUG] Created Resource Rate Limit Quota %s", name)

return quotaRateLimitRead(d, meta)
}

func quotaRateLimitRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

name := d.Id()
path := quotaRateLimitPath(name)

log.Printf("[DEBUG] Reading Resource Rate Limit Quota %s", name)
resp, err := client.Logical().Read(path)
if err != nil {
return fmt.Errorf("error reading Resource Rate Limit Quota %s: %s", name, err)
}

if resp == nil {
log.Printf("[WARN] Resource Rate Limit Quota %s not found, removing from state", name)
d.SetId("")
return nil
}

for _, k := range []string{"path", "rate"} {
v, ok := resp.Data[k]
if ok {
if err := d.Set(k, v); err != nil {
return fmt.Errorf("error setting %s for Resource Rate Limit Quota %s: %q", k, name, err)
}
}
}

return nil
}

func quotaRateLimitUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

name := d.Id()
path := quotaRateLimitPath(name)

log.Printf("[DEBUG] Updating Resource Rate Limit Quota %s", name)

data := map[string]interface{}{}
data["path"] = d.Get("path").(string)
data["rate"] = d.Get("rate").(float64)

_, err := client.Logical().Write(path, data)
if err != nil {
d.SetId("")
return fmt.Errorf("Error updating Resource Rate Limit Quota %s: %s", name, err)
}
log.Printf("[DEBUG] Updated Resource Rate Limit Quota %s", name)

return quotaRateLimitRead(d, meta)
}

func quotaRateLimitDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

name := d.Id()
path := quotaRateLimitPath(name)

log.Printf("[DEBUG] Deleting Resource Rate Limit Quota %s", name)
_, err := client.Logical().Delete(path)
if err != nil {
return fmt.Errorf("Error deleting Resource Rate Limit Quota %s", name)
}
log.Printf("[DEBUG] Deleted Resource Rate Limit Quota %s", name)

return nil
}

func quotaRateLimitExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*api.Client)

name := d.Id()
path := quotaRateLimitPath(name)

log.Printf("[DEBUG] Checking if Resource Rate Limit Quota %s exists", name)

secret, err := client.Logical().Read(path)
if err != nil {
return true, fmt.Errorf("error checking if Resource Rate Limit Quota %s exists: %s", name, err)
}

log.Printf("[DEBUG] Checked if Resource Rate Limit Quota %s exists", name)
return secret != nil, nil
}
89 changes: 89 additions & 0 deletions vault/resource_quota_rate_limit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package vault

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/hashicorp/vault/api"
)

func randomQuotaRateString() string {
whole := float64(acctest.RandIntRange(1000, 2000))
decimal := float64(acctest.RandIntRange(0, 100)) / 100

rateLimt := fmt.Sprintf("%.1f", whole+decimal)
// Vault retuns floats with trailing zeros trimmed
return strings.TrimRight(strings.TrimRight(rateLimt, "0"), ".")
}

func TestQuotaRateLimit(t *testing.T) {
name := acctest.RandomWithPrefix("tf-test")
rateLimit := randomQuotaRateString()
newRateLimit := randomQuotaRateString()
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testQuotaRateLimitCheckDestroy([]string{rateLimit, newRateLimit}),
Steps: []resource.TestStep{
{
Config: testQuotaRateLimit_Config(name, "", rateLimit),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "name", name),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "path", ""),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "rate", rateLimit),
),
},
{
Config: testQuotaRateLimit_Config(name, "", newRateLimit),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "name", name),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "path", ""),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "rate", newRateLimit),
),
},
{
Config: testQuotaRateLimit_Config(name, "sys/", newRateLimit),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "name", name),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "path", "sys/"),
resource.TestCheckResourceAttr("vault_quota_rate_limit.foobar", "rate", newRateLimit),
),
},
},
})
}

func testQuotaRateLimitCheckDestroy(rateLimits []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)

for _, name := range rateLimits {
resp, err := client.Logical().Read(quotaRateLimitPath(name))

if err != nil {
return err
}

if resp != nil {
return fmt.Errorf("Resource Quota Rate Limit %s still exists", name)
}
}

return nil
}
}

// Caution: Don't set test rate values too low or other tests running concurrently might fail
func testQuotaRateLimit_Config(name, path, rate string) string {
return fmt.Sprintf(`
resource "vault_quota_rate_limit" "foobar" {
name = "%s"
path = "%s"
rate = %s
}
`, name, path, rate)
}
54 changes: 54 additions & 0 deletions website/docs/r/quota_rate_limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
layout: "vault"
page_title: "Vault: vault_quota_rate_limit resource"
sidebar_current: "docs-vault-quota-rate-limit"
description: |-
Manage Rate Limit Quota
---

# vault\_quota\_rate\_limit

Manage rate limit quotas which enforce API rate limiting using a token bucket algorithm.
A rate limit quota can be created at the root level or defined on a namespace or mount by
specifying a path when creating the quota.

See [Vault's Documentation](https://www.vaultproject.io/docs/concepts/resource-quotas) for more
information.

## Example Usage

```hcl
resource "vault_quota_rate_limit" "global" {
name = "global"
path = ""
rate = 100
}
```

## Argument Reference

The following arguments are supported:

* `name` - (Required) Name of the rate limit quota

* `path` - (Optional) Path of the mount or namespace to apply the quota. A blank path configures a
global rate limit quota. For example `namespace1/` adds a quota to a full namespace,
`namespace1/auth/userpass` adds a `quota` to `userpass` in `namespace1`.
Updating this field on an existing quota can have "moving" effects. For example, updating
`auth/userpass` to `namespace1/auth/userpass` moves this quota from being a global mount quota to
a namespace specific mount quota. **Note, namespaces are supported in Enterprise only.**

* `rate` - (Required) The maximum number of requests at any given second to be allowed by the quota
rule. The `rate` must be positive.

## Attributes Reference

No additional attributes are exported by this resource.

## Import

Rate limit quotas can be imported using their names

```
$ terraform import vault_quota_rate_limit.global global
```
4 changes: 4 additions & 0 deletions website/vault.erb
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@
<a href="/docs/providers/vault/r/rgp_policy.html">vault_rgp_policy</a>
</li>

<li<%= sidebar_current("docs-vault-quota-rate-limit") %>>
<a href="/docs/providers/vault/r/quota_rate_limit.html">vault_quota_rate_limit</a>
</li>

<li<%= sidebar_current("docs-vault-resource-token") %>>
<a href="/docs/providers/vault/r/token.html">vault_token</a>
</li>
Expand Down

0 comments on commit 7c7ece4

Please sign in to comment.