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

Add vault_quota_rate_limit resource #825

Merged
merged 7 commits into from
Sep 10, 2020
Merged
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
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