Skip to content

Commit

Permalink
Merge pull request #127 from maxlaverse/data_source_with_filters
Browse files Browse the repository at this point in the history
implement support for filters in data sources
  • Loading branch information
maxlaverse authored Apr 20, 2024
2 parents 28abbd6 + fb64a7f commit aa13e7c
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 45 deletions.
14 changes: 12 additions & 2 deletions docs/data-sources/item_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this data source to get information on an existing Login.
## Example Usage

```terraform
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "Mysql Root Credentials" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -25,6 +25,11 @@ data "bitwarden_item_login" "database_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
}
# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}
# Later to be accessed as
# ... = data.bitwarden_item_login.database_credentials.username
# ... = data.bitwarden_item_login.database_credentials.password
Expand All @@ -42,9 +47,14 @@ data "bitwarden_item_login" "database_credentials" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required
### Optional

- `filter_collection_id` (String) Filter search results by collection ID
- `filter_folder_id` (String) Filter search results by folder ID
- `filter_organization_id` (String) Filter search results by organization ID
- `filter_url` (String) Filter search results by URL
- `id` (String) Identifier.
- `search` (String) Search items matching the search string. Can be combined with filters to narrow down the search.

### Read-Only

Expand Down
13 changes: 11 additions & 2 deletions docs/data-sources/item_secure_note.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this data source to get information on an existing Secure Note.
## Example Usage

```terraform
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "SSH Private Key" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -25,6 +25,11 @@ data "bitwarden_item_secure_note" "ssh_notes" {
id = "a9e19f26-1b8c-4568-bc09-191e2cf56ed6"
}
# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}
# Later to be accessed as
# ... = data.bitwarden_item_secure_note.ssh_notes.notes
#
Expand All @@ -41,9 +46,13 @@ data "bitwarden_item_secure_note" "ssh_notes" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required
### Optional

- `filter_collection_id` (String) Filter search results by collection ID
- `filter_folder_id` (String) Filter search results by folder ID
- `filter_organization_id` (String) Filter search results by organization ID
- `id` (String) Identifier.
- `search` (String) Search items matching the search string. Can be combined with filters to narrow down the search.

### Read-Only

Expand Down
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ resource "bitwarden_item_login" "example" {
username = "service-account"
password = "<sensitive>"
}
# Use Bitwarden Resource
data "bitwarden_item_login" "example" {
search = "Example"
}
```

## Authentication
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/item_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ resource "bitwarden_item_login" "administrative-user" {
- `favorite` (Boolean) Mark as a Favorite to have item appear at the top of your Vault in the UI.
- `field` (Block List) Extra fields. (see [below for nested schema](#nestedblock--field))
- `folder_id` (String) Identifier of the folder.
- `id` (String) Identifier.
- `notes` (String, Sensitive) Notes.
- `organization_id` (String) Identifier of the organization.
- `password` (String, Sensitive) Login password.
Expand All @@ -67,7 +68,6 @@ resource "bitwarden_item_login" "administrative-user" {
- `attachments` (List of Object) List of item attachments. (see [below for nested schema](#nestedatt--attachments))
- `creation_date` (String) Date the item was created.
- `deleted_date` (String) Date the item was deleted.
- `id` (String) Identifier.
- `revision_date` (String) Last time the item was updated.

<a id="nestedblock--field"></a>
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/item_secure_note.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ EOT
- `favorite` (Boolean) Mark as a Favorite to have item appear at the top of your Vault in the UI.
- `field` (Block List) Extra fields. (see [below for nested schema](#nestedblock--field))
- `folder_id` (String) Identifier of the folder.
- `id` (String) Identifier.
- `notes` (String, Sensitive) Notes.
- `organization_id` (String) Identifier of the organization.
- `reprompt` (Boolean) Require master password “re-prompt” when displaying secret in the UI.
Expand All @@ -64,7 +65,6 @@ EOT
- `attachments` (List of Object) List of item attachments. (see [below for nested schema](#nestedatt--attachments))
- `creation_date` (String) Date the item was created.
- `deleted_date` (String) Date the item was deleted.
- `id` (String) Identifier.
- `revision_date` (String) Last time the item was updated.

<a id="nestedblock--field"></a>
Expand Down
7 changes: 6 additions & 1 deletion examples/data-sources/bitwarden_item_login/data-source.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "Mysql Root Credentials" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -10,6 +10,11 @@ data "bitwarden_item_login" "database_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_login.database_credentials.username
# ... = data.bitwarden_item_login.database_credentials.password
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "SSH Private Key" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -10,6 +10,11 @@ data "bitwarden_item_secure_note" "ssh_notes" {
id = "a9e19f26-1b8c-4568-bc09-191e2cf56ed6"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_secure_note.ssh_notes.notes
#
Expand Down
2 changes: 1 addition & 1 deletion examples/quick/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ resource "bitwarden_item_secure_note" "vpn_note" {
# Read sensitive information from Bitwarden
# Using Login information
data "bitwarden_item_login" "mysql_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
search = "mysql/server-1"
}

# Later to be accessed as
Expand Down
5 changes: 5 additions & 0 deletions examples/quick/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ resource "bitwarden_item_login" "example" {
name = "Example"
username = "service-account"
password = "<sensitive>"
}

# Use Bitwarden Resource
data "bitwarden_item_login" "example" {
search = "Example"
}
26 changes: 26 additions & 0 deletions internal/bitwarden/bw/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Client interface {
GetObject(objType, itemOrSearch string) (*Object, error)
GetSessionKey() string
HasSessionKey() bool
ListObjects(objType string, options ...ListObjectsOption) ([]Object, error)
LoginWithAPIKey(password, clientId, clientSecret string) error
LoginWithPassword(username, password string) error
Logout() error
Expand Down Expand Up @@ -165,6 +166,31 @@ func (c *client) GetSessionKey() string {
return c.sessionKey
}

// ListObjects returns objects of a given type matching given filters.
func (c *client) ListObjects(objType string, options ...ListObjectsOption) ([]Object, error) {
args := []string{
"list",
objType,
}

for _, applyOption := range options {
applyOption(&args)
}

out, err := c.cmdWithSession(args...).Run()
if err != nil {
return nil, remapError(err)
}

var obj []Object
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, newUnmarshallError(err, "list object", out)
}

return obj, nil
}

// LoginWithPassword logs in using a password and retrieves the session key,
// allowing authenticated requests using the client.
func (c *client) LoginWithPassword(username, password string) error {
Expand Down
34 changes: 34 additions & 0 deletions internal/bitwarden/bw/client_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bw

type ListObjectsOption func(args *[]string)
type ListObjectsOptionGenerator func(id string) ListObjectsOption

func WithCollectionID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--collectionid", id)
}
}

func WithFolderID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--folderid", id)
}
}

func WithOrganizationID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--organizationid", id)
}
}

func WithSearch(search string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--search", search)
}
}

func WithUrl(url string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--url", url)
}
}
15 changes: 15 additions & 0 deletions internal/bitwarden/bw/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,18 @@ func TestCreateObjectEncoding(t *testing.T) {
assert.Equal(t, "create e30K", commandsExecuted()[1])
}
}

func TestListObjects(t *testing.T) {
removeMocks, commandsExecuted := test_command.MockCommands(t, map[string]string{
"list item --folderid folder-id --collectionid collection-id --search search": `[]`,
})
defer removeMocks(t)

b := NewClient("dummy")
_, err := b.ListObjects("item", WithFolderID("folder-id"), WithCollectionID("collection-id"), WithSearch("search"))

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
assert.Equal(t, "list item --folderid folder-id --collectionid collection-id --search search", commandsExecuted()[0])
}
}
15 changes: 15 additions & 0 deletions internal/bitwarden/bw/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bw

func FilterObjectsByType(objs []Object, itemType ItemType) []Object {
if itemType == 0 {
return objs
}

filtered := make([]Object, 0, len(objs))
for _, obj := range objs {
if obj.Type == itemType {
filtered = append(filtered, obj)
}
}
return filtered
}
72 changes: 72 additions & 0 deletions internal/provider/data_source_item_login_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"fmt"
"regexp"
"testing"

Expand Down Expand Up @@ -55,6 +56,77 @@ func TestAccDataSourceItemLoginFailsOnWrongResourceType(t *testing.T) {
})
}

func TestAccDataSourceItemLoginBySearch(t *testing.T) {
resourceName := "bitwarden_item_login.foo"

ensureVaultwardenConfigured(t)

resource.UnitTest(t, resource.TestCase{
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin(),
Check: checkItemLogin(resourceName),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigDataItemLoginWithSearchAndOrg("test-username"),
Check: checkItemLogin("data.bitwarden_item_login.foo_data"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigResourceItemLoginDuplicate() + tfConfigDataItemLoginWithSearchAndOrg("test-username"),
Check: checkItemLogin("data.bitwarden_item_login.foo_data"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigResourceItemLoginDuplicate() + tfConfigDataItemLoginWithSearchOnly("test-username"),
ExpectError: regexp.MustCompile("Error: too many objects found"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigDataItemLoginWithSearchAndOrg("missing-item"),
ExpectError: regexp.MustCompile("Error: no object found matching the filter"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemSecureNote(),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemSecureNote() + tfConfigDataItemLoginWithSearchAndOrg("secure-bar"),
ExpectError: regexp.MustCompile("Error: no object found matching the filter"),
},
},
})
}

func tfConfigDataItemLoginWithSearchAndOrg(search string) string {
return fmt.Sprintf(`
data "bitwarden_item_login" "foo_data" {
provider = bitwarden
search = "%s"
filter_organization_id = "%s"
}
`, search, testOrganizationID)
}

func tfConfigDataItemLoginWithSearchOnly(search string) string {
return fmt.Sprintf(`
data "bitwarden_item_login" "foo_data" {
provider = bitwarden
search = "%s"
}
`, search)
}

func tfConfigResourceItemLoginDuplicate() string {
return `
resource "bitwarden_item_login" "foo_duplicate" {
provider = bitwarden
name = "another item with username 'test-username'"
username = "test-username"
}
`
}

func tfConfigDataItemLogin() string {
return `
data "bitwarden_item_login" "foo_data" {
Expand Down
Loading

0 comments on commit aa13e7c

Please sign in to comment.