Skip to content
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
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ linters:
- "github.com/google/uuid$"
- "github.com/DependencyTrack/client-go$"
- "terraform-provider-dependencytrack/internal/provider$"

dogsled:
max-blank-identifiers: 2 # Default 2
dupl:
Expand Down Expand Up @@ -585,9 +584,11 @@ linters:
- path: internal/provider/tag_projects_resource.go
linters:
- gocognit
- godox
- path: internal/provider/tag_policies_resource.go
linters:
- gocognit
- godox
- path: internal/provider/component_resource.go
linters:
- revive
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 1.17.2

#### FIXES
- `tags` in `dependencytrack_project` no longer needs to be sorted.
- https://github.com/SolarFactories/terraform-provider-dependencytrack/issues/152

#### DEPENDENCIES
- `actions/checkout` `6.0.1` -> `6.0.2`

## 1.17.1

#### FIXES
Expand Down
30 changes: 24 additions & 6 deletions internal/provider/project_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
"strings"

dtrack "github.com/DependencyTrack/client-go"
"github.com/hashicorp/terraform-plugin-framework/attr"
Expand Down Expand Up @@ -174,7 +175,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
}
}
if !plan.Tags.IsUnknown() && !plan.Tags.IsNull() {
strings, err := GetStringList(ctx, &resp.Diagnostics, plan.Tags)
tagStrings, err := GetStringList(ctx, &resp.Diagnostics, plan.Tags)
if resp.Diagnostics.HasError() {
return
}
Expand All @@ -186,7 +187,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
)
return
}
projectReq.Tags = Map(strings, func(item string) dtrack.Tag { return dtrack.Tag{Name: item} })
projectReq.Tags = Map(tagStrings, func(item string) dtrack.Tag { return dtrack.Tag{Name: item} })
}
if plan.Active.IsUnknown() {
projectReq.Active = true
Expand Down Expand Up @@ -228,7 +229,13 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
)
return
}
tagValueList := Map(projectRes.Tags, func(tag dtrack.Tag) attr.Value {
resTags := projectRes.Tags
if SliceUnorderedEqual(projectReq.Tags, projectRes.Tags, func(req, res dtrack.Tag) int {
return strings.Compare(req.Name, res.Name)
}) {
resTags = projectReq.Tags
}
tagValueList := Map(resTags, func(tag dtrack.Tag) attr.Value {
return types.StringValue(tag.Name)
})
tagList, diags := types.ListValue(types.StringType, tagValueList)
Expand Down Expand Up @@ -337,9 +344,20 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
return
}

tagValueList := Map(project.Tags, func(tag dtrack.Tag) attr.Value {
return types.StringValue(tag.Name)
})
stateTags, err := GetStringList(ctx, &resp.Diagnostics, state.Tags)
if err != nil {
resp.Diagnostics.AddError(
"Unable to load current tags on project",
"Error with transforming stored tags on project: "+id.String()+", in original error: "+err.Error(),
)
return
}
returnedTags := Map(project.Tags, func(tag dtrack.Tag) string { return tag.Name })
newStateTags := returnedTags
if SliceUnorderedEqual(stateTags, returnedTags, strings.Compare) {
newStateTags = stateTags
}
tagValueList := Map(newStateTags, func(name string) attr.Value { return types.StringValue(name) })
tagList, diags := types.ListValue(types.StringType, tagValueList)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
Expand Down
45 changes: 45 additions & 0 deletions internal/provider/project_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,48 @@ resource "dependencytrack_project" "test3" {
},
})
}

func TestAccProjectResourceRegression152(t *testing.T) {
// Regression test for https://github.com/SolarFactories/terraform-provider-dependencytrack/issues/152
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing.
{
Config: providerConfig + `
resource "dependencytrack_project" "example" {
name = "example-project"
description = "Example project"
tags = [
"collection-example",
"environment-test"
]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.#", "2"),
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.0", "collection-example"),
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.1", "environment-test"),
),
},
// Update and Read testing.
{
Config: providerConfig + `
resource "dependencytrack_project" "example" {
name = "example-project"
description = "Example project With Change"
tags = [
"collection-example",
"environment-test"
]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.#", "2"),
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.0", "collection-example"),
resource.TestCheckResourceAttr("dependencytrack_project.example", "tags.1", "environment-test"),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/provider/tag_policies_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (*tagPoliciesResource) Schema(_ context.Context, _ resource.SchemaRequest,
},
},
"policies": schema.ListAttribute{
// TODO: Use `ListUnorderedEqual` to remove requirement for this to be sorted.
Description: "Policy UUIDs to which to apply tag. Sorted by policy name.",
Required: true,
ElementType: types.StringType,
Expand Down
1 change: 1 addition & 0 deletions internal/provider/tag_projects_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (*tagProjectsResource) Schema(_ context.Context, _ resource.SchemaRequest,
},
},
"projects": schema.ListAttribute{
// TODO: Use `ListUnorderedEqual` to remove requirement for this to be sorted.
Description: "Project UUIDs to which to apply tag. Sorted by project name.",
Required: true,
ElementType: types.StringType,
Expand Down
6 changes: 6 additions & 0 deletions internal/provider/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,9 @@ func GetStringList(ctx context.Context, diags *diag.Diagnostics, list types.List
})
return stringList, err
}

func SliceUnorderedEqual[T any](a, b []T, compare func(a, b T) int) bool {
sortedA := slices.SortedStableFunc(slices.Values(a), compare)
sortedB := slices.SortedStableFunc(slices.Values(b), compare)
return slices.EqualFunc(sortedA, sortedB, func(a, b T) bool { return compare(a, b) == 0 })
}
30 changes: 29 additions & 1 deletion internal/provider/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"cmp"
"regexp"
"strings"
"testing"
)

Expand Down Expand Up @@ -63,6 +64,33 @@ func TestParseSemver(t *testing.T) {
}
}

func TestSliceUnorderedEqual(t *testing.T) {
{
a := []int{1, 20, 15, 18}
b := []int{1, 15, 18, 20}
equal := SliceUnorderedEqual(a, b, func(a, b int) int { return b - a })
requireEqual(t, equal, true)
}
{
a := []int{1, 20, 15, 19}
b := []int{1, 15, 18, 20}
equal := SliceUnorderedEqual(a, b, func(a, b int) int { return b - a })
requireEqual(t, equal, false)
}
{
a := []int{}
b := []int{}
equal := SliceUnorderedEqual(a, b, func(a, b int) int { return b - a })
requireEqual(t, equal, true)
}
{
a := []string{"Foo", "Bar"}
b := []string{"Bar", "Foo"}
equal := SliceUnorderedEqual(a, b, strings.Compare)
requireEqual(t, equal, true)
}
}

func requireNoError(t *testing.T, actual error) {
t.Helper()
if actual != nil {
Expand Down Expand Up @@ -93,7 +121,7 @@ func requireNil[T any](t *testing.T, actual *T) {
}
}

func requireEqual[T cmp.Ordered](t *testing.T, actual T, expected T) {
func requireEqual[T cmp.Ordered | bool](t *testing.T, actual T, expected T) {
t.Helper()
if actual != expected {
t.Errorf("Expected %v, received %v", expected, actual)
Expand Down