Skip to content

Commit

Permalink
Address review comments and implement the functionality for files wit…
Browse files Browse the repository at this point in the history
…hin sections
  • Loading branch information
sdahlbac committed Jun 5, 2024
1 parent f33a1e5 commit 348568d
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 23 deletions.
14 changes: 13 additions & 1 deletion docs/data-sources/item.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Read-Only:

- `content` (String, Sensitive) The content of the file.
- `content_base64` (String, Sensitive) The content of the file in base64 encoding. (Use this for binary files.)
- `id` (String) A UUID for the file.
- `id` (String) The UUID for the file.
- `name` (String) The name of the file.


Expand All @@ -64,6 +64,7 @@ Read-Only:
Read-Only:

- `field` (Block List) (see [below for nested schema](#nestedblock--section--field))
- `file` (Block List) A list of files attached to the section. (see [below for nested schema](#nestedblock--section--file))
- `id` (String) A unique identifier for the section.
- `label` (String) The label for the section.

Expand All @@ -77,3 +78,14 @@ Read-Only:
- `purpose` (String) Purpose indicates this is a special field: a username, password, or notes field.
- `type` (String) The type of value stored in the field.
- `value` (String, Sensitive) The value of the field.


<a id="nestedblock--section--file"></a>
### Nested Schema for `section.file`

Read-Only:

- `content` (String, Sensitive) The content of the file.
- `content_base64` (String, Sensitive) The content of the file in base64 encoding. (Use this for binary files.)
- `id` (String) The UUID for the file.
- `name` (String) The name of the file.
2 changes: 1 addition & 1 deletion docs/resources/item.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ resource "onepassword_item" "demo_db" {

### Optional

- `category` (String) The category of the item. One of ["login" "password" "database" "secure_note" "document"]
- `category` (String) The category of the item. One of ["login" "password" "database" "secure_note"]
- `database` (String) (Only applies to the database category) The name of the database.
- `hostname` (String) (Only applies to the database category) The address where the database can be found
- `note_value` (String, Sensitive) Secure Note value.
Expand Down
5 changes: 3 additions & 2 deletions internal/provider/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ const (
sectionIDDescription = "A unique identifier for the section."
sectionLabelDescription = "The label for the section."
sectionFieldsDescription = "A list of custom fields in the section."
sectionFilesDescription = "A list of files attached to the section."

filesDescription = "A list of files attached to the item."
fileDescription = "A file attached to the item."
fileIDDescription = "A UUID for the file."
fileIDDescription = "The UUID for the file."
fileNameDescription = "The name of the file."
fileContentDescription = "The content of the file."
fileContentBase64Description = "The content of the file in base64 encoding. (Use this for binary files.)"
Expand Down Expand Up @@ -64,8 +65,8 @@ var (
strings.ToLower(string(op.Password)),
strings.ToLower(string(op.Database)),
strings.ToLower(string(op.SecureNote)),
strings.ToLower(string(op.Document)),
}
dataSourceCategories = append(categories, strings.ToLower(string(op.Document)))

fieldPurposes = []string{
string(op.FieldPurposeUsername),
Expand Down
74 changes: 63 additions & 11 deletions internal/provider/onepassword_item_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type OnePasswordItemSectionModel struct {
ID types.String `tfsdk:"id"`
Label types.String `tfsdk:"label"`
Field []OnePasswordItemFieldModel `tfsdk:"field"`
File []OnePasswordItemFileModel `tfsdk:"file"`
}

type OnePasswordItemFieldModel struct {
Expand Down Expand Up @@ -107,7 +108,7 @@ func (d *OnePasswordItemDataSource) Schema(ctx context.Context, req datasource.S
Computed: true,
},
"category": schema.StringAttribute{
MarkdownDescription: fmt.Sprintf(enumDescription, categoryDescription, categories),
MarkdownDescription: fmt.Sprintf(enumDescription, categoryDescription, dataSourceCategories),
Computed: true,
},
"url": schema.StringAttribute{
Expand Down Expand Up @@ -193,6 +194,31 @@ func (d *OnePasswordItemDataSource) Schema(ctx context.Context, req datasource.S
},
},
},
"file": schema.ListNestedBlock{
MarkdownDescription: sectionFilesDescription,
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: fileIDDescription,
Computed: true,
},
"name": schema.StringAttribute{
MarkdownDescription: fileNameDescription,
Computed: true,
},
"content": schema.StringAttribute{
MarkdownDescription: fileContentDescription,
Computed: true,
Sensitive: true,
},
"content_base64": schema.StringAttribute{
MarkdownDescription: fileContentBase64Description,
Computed: true,
Sensitive: true,
},
},
},
},
},
},
},
Expand Down Expand Up @@ -301,6 +327,26 @@ func (d *OnePasswordItemDataSource) Read(ctx context.Context, req datasource.Rea
}
}

for _, f := range item.Files {
if f.Section != nil && f.Section.ID == s.ID {
content, err := f.Content()
if err != nil {
// content has not yet been loaded, fetch it
content, err = d.client.GetFileContent(ctx, f, item.ID, item.Vault.ID)
}
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read file, got error: %s", err))
}
file := OnePasswordItemFileModel{
ID: types.StringValue(f.ID),
Name: types.StringValue(f.Name),
Content: types.StringValue(string(content)),
ContentBase64: types.StringValue(base64.StdEncoding.EncodeToString(content)),
}
section.File = append(section.File, file)
}
}

data.Section = append(data.Section, section)
}

Expand Down Expand Up @@ -333,17 +379,23 @@ func (d *OnePasswordItemDataSource) Read(ctx context.Context, req datasource.Rea
}

for _, f := range item.Files {
content, err := d.client.GetFileContent(ctx, f, item.ID, item.Vault.ID)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read file, got error: %s", err))
}
file := OnePasswordItemFileModel{
ID: types.StringValue(f.ID),
Name: types.StringValue(f.Name),
Content: types.StringValue(string(content)),
ContentBase64: types.StringValue(base64.StdEncoding.EncodeToString(content)),
if f.Section == nil {
content, err := f.Content()
if err != nil {
// content has not yet been loaded, fetch it
content, err = d.client.GetFileContent(ctx, f, item.ID, item.Vault.ID)
}
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read file, got error: %s", err))
}
file := OnePasswordItemFileModel{
ID: types.StringValue(f.ID),
Name: types.StringValue(f.Name),
Content: types.StringValue(string(content)),
ContentBase64: types.StringValue(base64.StdEncoding.EncodeToString(content)),
}
data.File = append(data.File, file)
}
data.File = append(data.File, file)
}
// Write logs using the tflog package
// Documentation: https://terraform.io/plugin/log
Expand Down
44 changes: 44 additions & 0 deletions internal/provider/onepassword_item_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,50 @@ func TestAccItemDocument(t *testing.T) {
})
}

func TestAccItemLoginWithFiles(t *testing.T) {
expectedItem := generateLoginItemWithFiles()
expectedVault := op.Vault{
ID: expectedItem.Vault.ID,
Name: "Name of the vault",
Description: "This vault will be retrieved",
}

testServer := setupTestServer(expectedItem, expectedVault, t)
defer testServer.Close()

first_content, err := expectedItem.Files[0].Content()
if err != nil {
t.Fatalf("Error getting content of first file: %v", err)
}

second_content, err := expectedItem.Files[1].Content()
if err != nil {
t.Fatalf("Error getting content of second file: %v", err)
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccProviderConfig(testServer.URL) + testAccItemDataSourceConfig(expectedItem.Vault.ID, expectedItem.ID),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.onepassword_item.test", "id", fmt.Sprintf("vaults/%s/items/%s", expectedVault.ID, expectedItem.ID)),
resource.TestCheckResourceAttr("data.onepassword_item.test", "vault", expectedVault.ID),
resource.TestCheckResourceAttr("data.onepassword_item.test", "title", expectedItem.Title),
resource.TestCheckResourceAttr("data.onepassword_item.test", "uuid", expectedItem.ID),
resource.TestCheckResourceAttr("data.onepassword_item.test", "category", strings.ToLower(string(expectedItem.Category))),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.0.id", expectedItem.Files[0].ID),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.0.name", expectedItem.Files[0].Name),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.0.content", string(first_content)),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.1.id", expectedItem.Files[1].ID),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.1.name", expectedItem.Files[1].Name),
resource.TestCheckResourceAttr("data.onepassword_item.test", "section.0.file.1.content_base64", base64.StdEncoding.EncodeToString(second_content)),
),
},
},
})
}

func testAccItemDataSourceConfig(vault, uuid string) string {
return fmt.Sprintf(`
data "onepassword_item" "test" {
Expand Down
2 changes: 0 additions & 2 deletions internal/provider/onepassword_item_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ func (r *OnePasswordItemResource) Schema(ctx context.Context, req resource.Schem
Default: stringdefault.StaticString("login"),
Validators: []validator.String{
stringvalidator.OneOfCaseInsensitive(categories...),
// connect api does not support creating document category
stringvalidator.NoneOf("document"),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
Expand Down
9 changes: 5 additions & 4 deletions internal/provider/onepassword_item_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,11 @@ func TestAccItemResourceDocument(t *testing.T) {

func testAccDataBaseResourceConfig(expectedItem *op.Item) string {
return fmt.Sprintf(`
data "onepassword_vault" "acceptance-tests" {
uuid = "%s"
}
resource "onepassword_item" "test-database" {
data "onepassword_vault" "acceptance-tests" {
uuid = "%s"
}
resource "onepassword_item" "test-database" {
vault = data.onepassword_vault.acceptance-tests.uuid
title = "%s"
category = "%s"
Expand Down
5 changes: 3 additions & 2 deletions internal/provider/test_http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"regexp"
"slices"
"strings"
"testing"
Expand Down Expand Up @@ -42,6 +43,7 @@ func setupTestServer(expectedItem *onepassword.Item, expectedVault onepassword.V
}

return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
filePath := regexp.MustCompile("/v1/vaults/[a-z0-9]*/items/[a-z0-9]*/files/[a-z0-9]*/content")
if r.Method == http.MethodGet {
if r.URL.String() == fmt.Sprintf("/v1/vaults/%s/items/%s", expectedItem.Vault.ID, expectedItem.ID) {
// Mock returning an item specified by uuid
Expand All @@ -65,8 +67,7 @@ func setupTestServer(expectedItem *onepassword.Item, expectedVault onepassword.V
if err != nil {
t.Errorf("error writing body: %s", err)
}
} else if r.URL.String() == fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", expectedItem.Vault.ID, expectedItem.ID, files[0].ID) ||
r.URL.String() == fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", expectedItem.Vault.ID, expectedItem.ID, files[1].ID) {
} else if filePath.MatchString(r.URL.String()) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("1Password-Connect-Version", "1.3.0") // must be >= 1.3.0
i := slices.IndexFunc(files, func(f *onepassword.File) bool {
Expand Down
24 changes: 24 additions & 0 deletions internal/provider/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ func generateDocumentItem() *onepassword.Item {
return &item
}

func generateLoginItemWithFiles() *onepassword.Item {
item := generateItemWithSections()
item.Category = onepassword.Login
section := item.Sections[0]
item.Files = []*onepassword.File{
{
ID: "ascii",
Name: "ascii",
Section: section,
ContentPath: fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", item.Vault.ID, item.ID, "ascii"),
},
{
ID: "binary",
Name: "binary",
Section: section,
ContentPath: fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", item.Vault.ID, item.ID, "binary"),
},
}
item.Files[0].SetContent([]byte("ascii"))
item.Files[1].SetContent([]byte{0xDE, 0xAD, 0xBE, 0xEF})

return item
}

func generateDatabaseFields() []*onepassword.ItemField {
fields := []*onepassword.ItemField{
{
Expand Down

0 comments on commit 348568d

Please sign in to comment.