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

test against recent cli #148

Closed
wants to merge 10 commits into from
Closed
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: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

services:
vaultwarden:
image: vaultwarden/server:latest
image: vaultwarden/server:1.32.0
env:
ADMIN_TOKEN: test1234
I_REALLY_WANT_VOLATILE_STORAGE: "true"
Expand All @@ -50,7 +50,7 @@ jobs:
- id: binaries
run: |
echo "LOCAL_BINARIES=$GITHUB_WORKSPACE/bin" >> $GITHUB_OUTPUT
echo "BWCLI_VERSION=2023.2.0" >> $GITHUB_OUTPUT
echo "BWCLI_VERSION=2024.7.2" >> $GITHUB_OUTPUT

- name: Check out repository code
uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ terraform-lock

# Code coverage artifact
profile.cov
internal/provider/.bitwarden/
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ This project is not associated with the Bitwarden project nor 8bit Solutions LLC

## Supported Versions
The plugin has been tested and built with the following components:
- [Terraform] v1.6.1
- [Bitwarden CLI] v2023.2.0
- [Terraform] v1.6.1 / [OpenTofu] v1.8.0
- [Bitwarden CLI] 2024.7.2
- [Go] 1.22.0 (for development)
- [Docker] 23.0.5 (for development)

Expand Down Expand Up @@ -106,9 +106,10 @@ $ make testacc
Distributed under the Mozilla License. See [LICENSE](./LICENSE) for more information.

[Terraform]: https://www.terraform.io/downloads.html
[OpenTofu]: https://opentofu.org/docs/intro/install/
[Go]: https://golang.org/doc/install
[Bitwarden CLI]: https://bitwarden.com/help/article/cli/#download-and-install
[Docker]: https://www.docker.com/products/docker-desktop
[Terraform Registry docs]: https://registry.terraform.io/providers/maxlaverse/bitwarden/latest/docs
[hashicorp/terraform-plugin-sdk#63]: https://github.com/hashicorp/terraform-plugin-sdk/issues/63
[Terraform's documentation on Data Storage]: https://bitwarden.com/help/data-storage/#on-your-local-machine
[Terraform's documentation on Data Storage]: https://bitwarden.com/help/data-storage/#on-your-local-machine
103 changes: 50 additions & 53 deletions internal/bitwarden/bw/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package bw

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand All @@ -9,24 +11,24 @@ import (
)

type Client interface {
CreateAttachment(itemId, filePath string) (*Object, error)
CreateObject(Object) (*Object, error)
EditObject(Object) (*Object, error)
GetAttachment(itemId, attachmentId string) ([]byte, error)
GetObject(Object) (*Object, error)
CreateAttachment(ctx context.Context, itemId, filePath string) (*Object, error)
CreateObject(context.Context, Object) (*Object, error)
EditObject(context.Context, Object) (*Object, error)
GetAttachment(ctx context.Context, itemId, attachmentId string) ([]byte, error)
GetObject(context.Context, Object) (*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
DeleteAttachment(itemId, attachmentId string) error
DeleteObject(Object) error
SetServer(string) error
ListObjects(ctx context.Context, objType string, options ...ListObjectsOption) ([]Object, error)
LoginWithAPIKey(ctx context.Context, password, clientId, clientSecret string) error
LoginWithPassword(ctx context.Context, username, password string) error
Logout(context.Context) error
DeleteAttachment(ctx context.Context, itemId, attachmentId string) error
DeleteObject(context.Context, Object) error
SetServer(context.Context, string) error
SetSessionKey(string)
Status() (*Status, error)
Sync() error
Unlock(password string) error
Status(context.Context) (*Status, error)
Sync(context.Context) error
Unlock(ctx context.Context, password string) error
}

func NewClient(execPath string, opts ...Options) Client {
Expand Down Expand Up @@ -79,7 +81,7 @@ func DisableRetryBackoff() Options {
}
}

func (c *client) CreateObject(obj Object) (*Object, error) {
func (c *client) CreateObject(ctx context.Context, obj Object) (*Object, error) {
objEncoded, err := c.encode(obj)
if err != nil {
return nil, err
Expand All @@ -95,7 +97,7 @@ func (c *client) CreateObject(obj Object) (*Object, error) {
args = append(args, "--organizationid", obj.OrganizationID)
}

out, err := c.cmdWithSession(args...).Run()
out, err := c.cmdWithSession(args...).Run(ctx)
if err != nil {
return nil, err
}
Expand All @@ -109,8 +111,8 @@ func (c *client) CreateObject(obj Object) (*Object, error) {
return &obj, nil
}

func (c *client) CreateAttachment(itemId string, filePath string) (*Object, error) {
out, err := c.cmdWithSession("create", string(ObjectTypeAttachment), "--itemid", itemId, "--file", filePath).Run()
func (c *client) CreateAttachment(ctx context.Context, itemId string, filePath string) (*Object, error) {
out, err := c.cmdWithSession("create", string(ObjectTypeAttachment), "--itemid", itemId, "--file", filePath).Run(ctx)
if err != nil {
return nil, err
}
Expand All @@ -126,29 +128,29 @@ func (c *client) CreateAttachment(itemId string, filePath string) (*Object, erro
return &obj, nil
}

func (c *client) EditObject(obj Object) (*Object, error) {
func (c *client) EditObject(ctx context.Context, obj Object) (*Object, error) {
objEncoded, err := c.encode(obj)
if err != nil {
return nil, err
}

out, err := c.cmdWithSession("edit", string(obj.Object), obj.ID, objEncoded).Run()
out, err := c.cmdWithSession("edit", string(obj.Object), obj.ID, objEncoded).Run(ctx)
if err != nil {
return nil, err
}
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, newUnmarshallError(err, "edit object", out)
}
err = c.Sync()
err = c.Sync(ctx)
if err != nil {
return nil, fmt.Errorf("error syncing: %v, %v", err, string(out))
}

return &obj, nil
}

func (c *client) GetObject(obj Object) (*Object, error) {
func (c *client) GetObject(ctx context.Context, obj Object) (*Object, error) {
args := []string{
"get",
string(obj.Object),
Expand All @@ -159,7 +161,7 @@ func (c *client) GetObject(obj Object) (*Object, error) {
args = append(args, "--organizationid", obj.OrganizationID)
}

out, err := c.cmdWithSession(args...).Run()
out, err := c.cmdWithSession(args...).Run(ctx)
if err != nil {
return nil, remapError(err)
}
Expand All @@ -172,8 +174,8 @@ func (c *client) GetObject(obj Object) (*Object, error) {
return &obj, nil
}

func (c *client) GetAttachment(itemId, attachmentId string) ([]byte, error) {
out, err := c.cmdWithSession("get", string(ObjectTypeAttachment), attachmentId, "--itemid", itemId, "--raw").Run()
func (c *client) GetAttachment(ctx context.Context, itemId, attachmentId string) ([]byte, error) {
out, err := c.cmdWithSession("get", string(ObjectTypeAttachment), attachmentId, "--itemid", itemId, "--raw").Run(ctx)
if err != nil {
return nil, remapError(err)
}
Expand All @@ -186,7 +188,7 @@ func (c *client) GetSessionKey() string {
}

// ListObjects returns objects of a given type matching given filters.
func (c *client) ListObjects(objType string, options ...ListObjectsOption) ([]Object, error) {
func (c *client) ListObjects(ctx context.Context, objType string, options ...ListObjectsOption) ([]Object, error) {
args := []string{
"list",
objType,
Expand All @@ -196,7 +198,7 @@ func (c *client) ListObjects(objType string, options ...ListObjectsOption) ([]Ob
applyOption(&args)
}

out, err := c.cmdWithSession(args...).Run()
out, err := c.cmdWithSession(args...).Run(ctx)
if err != nil {
return nil, remapError(err)
}
Expand All @@ -212,8 +214,8 @@ func (c *client) ListObjects(objType string, options ...ListObjectsOption) ([]Ob

// 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 {
out, err := c.cmd("login", username, "--raw", "--passwordenv", "BW_PASSWORD").AppendEnv([]string{fmt.Sprintf("BW_PASSWORD=%s", password)}).Run()
func (c *client) LoginWithPassword(ctx context.Context, username, password string) error {
out, err := c.cmd("login", username, "--raw", "--passwordenv", "BW_PASSWORD").AppendEnv([]string{fmt.Sprintf("BW_PASSWORD=%s", password)}).Run(ctx)
if err != nil {
return err
}
Expand All @@ -223,20 +225,20 @@ func (c *client) LoginWithPassword(username, password string) error {

// LoginWithPassword logs in using an API key and unlock the Vault in order to retrieve a session key,
// allowing authenticated requests using the client.
func (c *client) LoginWithAPIKey(password, clientId, clientSecret string) error {
_, err := c.cmd("login", "--apikey").AppendEnv([]string{fmt.Sprintf("BW_CLIENTID=%s", clientId), fmt.Sprintf("BW_CLIENTSECRET=%s", clientSecret)}).Run()
func (c *client) LoginWithAPIKey(ctx context.Context, password, clientId, clientSecret string) error {
_, err := c.cmd("login", "--apikey").AppendEnv([]string{fmt.Sprintf("BW_CLIENTID=%s", clientId), fmt.Sprintf("BW_CLIENTSECRET=%s", clientSecret)}).Run(ctx)
if err != nil {
return err
}
return c.Unlock(password)
return c.Unlock(ctx, password)
}

func (c *client) Logout() error {
_, err := c.cmd("logout").Run()
func (c *client) Logout(ctx context.Context) error {
_, err := c.cmd("logout").Run(ctx)
return err
}

func (c *client) DeleteObject(obj Object) error {
func (c *client) DeleteObject(ctx context.Context, obj Object) error {
args := []string{
"delete",
string(obj.Object),
Expand All @@ -247,22 +249,22 @@ func (c *client) DeleteObject(obj Object) error {
args = append(args, "--organizationid", obj.OrganizationID)
}

_, err := c.cmdWithSession(args...).Run()
_, err := c.cmdWithSession(args...).Run(ctx)
return err
}

func (c *client) DeleteAttachment(itemId, attachmentId string) error {
_, err := c.cmdWithSession("delete", string(ObjectTypeAttachment), attachmentId, "--itemid", itemId).Run()
func (c *client) DeleteAttachment(ctx context.Context, itemId, attachmentId string) error {
_, err := c.cmdWithSession("delete", string(ObjectTypeAttachment), attachmentId, "--itemid", itemId).Run(ctx)
return err
}

func (c *client) SetServer(server string) error {
_, err := c.cmd("config", "server", server).Run()
func (c *client) SetServer(ctx context.Context, server string) error {
_, err := c.cmd("config", "server", server).Run(ctx)
return err
}

func (c *client) Status() (*Status, error) {
out, err := c.cmdWithSession("status").Run()
func (c *client) Status(ctx context.Context) (*Status, error) {
out, err := c.cmdWithSession("status").Run(ctx)
if err != nil {
return nil, err
}
Expand All @@ -276,8 +278,8 @@ func (c *client) Status() (*Status, error) {
return &status, nil
}

func (c *client) Unlock(password string) error {
out, err := c.cmd("unlock", "--raw", "--passwordenv", "BW_PASSWORD").AppendEnv([]string{fmt.Sprintf("BW_PASSWORD=%s", password)}).Run()
func (c *client) Unlock(ctx context.Context, password string) error {
out, err := c.cmd("unlock", "--raw", "--passwordenv", "BW_PASSWORD").AppendEnv([]string{fmt.Sprintf("BW_PASSWORD=%s", password)}).Run(ctx)
if err != nil {
return err
}
Expand All @@ -294,11 +296,11 @@ func (c *client) SetSessionKey(sessionKey string) {
c.sessionKey = sessionKey
}

func (c *client) Sync() error {
func (c *client) Sync(ctx context.Context) error {
if c.disableSync {
return nil
}
_, err := c.cmdWithSession("sync").Run()
_, err := c.cmdWithSession("sync").Run(ctx)
return err
}

Expand Down Expand Up @@ -327,10 +329,5 @@ func (c *client) encode(item Object) (string, error) {
if err != nil {
return "", fmt.Errorf("marshalling error: %v, %v", err, string(newOut))
}

out, err := c.cmd("encode").WithStdin(string(newOut)).Run()
if err != nil {
return "", fmt.Errorf("encoding error: %v, %v", err, string(newOut))
}
return string(out), err
return base64.StdEncoding.EncodeToString(newOut), nil
}
17 changes: 8 additions & 9 deletions internal/bitwarden/bw/client_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bw

import (
"context"
"testing"

test_command "github.com/maxlaverse/terraform-provider-bitwarden/internal/command/test"
Expand All @@ -9,13 +10,12 @@ import (

func TestCreateObjectEncoding(t *testing.T) {
removeMocks, commandsExecuted := test_command.MockCommands(t, map[string]string{
"encode": `e30K`,
"create e30K": `{}`,
"create eyJncm91cHMiOm51bGwsImxvZ2luIjp7fSwic2VjdXJlTm90ZSI6e30sInR5cGUiOjEsImZpZWxkcyI6W3sibmFtZSI6InRlc3QiLCJ2YWx1ZSI6InBhc3NlZCIsInR5cGUiOjAsImxpbmtlZElkIjpudWxsfV19": `{}`,
})
defer removeMocks(t)

b := NewClient("dummy")
_, err := b.CreateObject(Object{
_, err := b.CreateObject(context.Background(), Object{
Type: ItemTypeLogin,
Fields: []Field{
{
Expand All @@ -27,9 +27,8 @@ func TestCreateObjectEncoding(t *testing.T) {
})

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 2) {
assert.Equal(t, "{\"groups\":null,\"login\":{},\"secureNote\":{},\"type\":1,\"fields\":[{\"name\":\"test\",\"value\":\"passed\",\"type\":0,\"linkedId\":null}]}:/:encode", commandsExecuted()[0])
assert.Equal(t, "create e30K", commandsExecuted()[1])
if assert.Len(t, commandsExecuted(), 1) {
assert.Equal(t, "create eyJncm91cHMiOm51bGwsImxvZ2luIjp7fSwic2VjdXJlTm90ZSI6e30sInR5cGUiOjEsImZpZWxkcyI6W3sibmFtZSI6InRlc3QiLCJ2YWx1ZSI6InBhc3NlZCIsInR5cGUiOjAsImxpbmtlZElkIjpudWxsfV19", commandsExecuted()[0])
}
}

Expand All @@ -40,7 +39,7 @@ func TestListObjects(t *testing.T) {
defer removeMocks(t)

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

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
Expand All @@ -55,7 +54,7 @@ func TestGetItem(t *testing.T) {
defer removeMocks(t)

b := NewClient("dummy")
_, err := b.GetObject(Object{ID: "object-id", Object: ObjectTypeItem, Type: ItemTypeLogin})
_, err := b.GetObject(context.Background(), Object{ID: "object-id", Object: ObjectTypeItem, Type: ItemTypeLogin})

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
Expand All @@ -70,7 +69,7 @@ func TestGetOrgCollection(t *testing.T) {
defer removeMocks(t)

b := NewClient("dummy")
_, err := b.GetObject(Object{ID: "object-id", Object: ObjectTypeOrgCollection, OrganizationID: "org-id"})
_, err := b.GetObject(context.Background(), Object{ID: "object-id", Object: ObjectTypeOrgCollection, OrganizationID: "org-id"})

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
Expand Down
2 changes: 1 addition & 1 deletion internal/bitwarden/bw/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
)

func newUnmarshallError(err error, cmd string, out []byte) error {
return fmt.Errorf("unable to parse result of '%s' command: %v, output: %v", cmd, err, string(out))
return fmt.Errorf("unable to parse result of '%s', error: '%v', output: '%v'", cmd, err, string(out))
}

func remapError(err error) error {
Expand Down
Loading
Loading