Skip to content

Commit f31a6ff

Browse files
Feature: Tag Resource lists unordered. (#157)
* Removed requirement for projects to be sorted within dependencytrack_tag_projects. * Remove need for policies to be sorted within dependencytrack_tag_policies resource. * Remove exceptions from linting config for resolved TODO's.
1 parent cf7c32d commit f31a6ff

File tree

7 files changed

+190
-39
lines changed

7 files changed

+190
-39
lines changed

.golangci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,11 +584,9 @@ linters:
584584
- path: internal/provider/tag_projects_resource.go
585585
linters:
586586
- gocognit
587-
- godox
588587
- path: internal/provider/tag_policies_resource.go
589588
linters:
590589
- gocognit
591-
- godox
592590
- path: internal/provider/component_resource.go
593591
linters:
594592
- revive

docs/resources/tag_policies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ resource "dependencytrack_tag_policies" "example" {
4040

4141
### Required
4242

43-
- `policies` (List of String) Policy UUIDs to which to apply tag. Sorted by policy name.
43+
- `policies` (List of String) Policy UUIDs to which to apply tag.
4444
- `tag` (String) Name of the Tag.
4545

4646
### Read-Only

docs/resources/tag_projects.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ resource "dependencytrack_tag_projects" "example" {
3838

3939
### Required
4040

41-
- `projects` (List of String) Project UUIDs to which to apply tag. Sorted by project name.
41+
- `projects` (List of String) Project UUIDs to which to apply tag.
4242
- `tag` (String) Name of the Tag. Must be lowercase.
4343

4444
### Read-Only

internal/provider/tag_policies_resource.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provider
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
dtrack "github.com/DependencyTrack/client-go"
89
"github.com/google/uuid"
@@ -61,8 +62,7 @@ func (*tagPoliciesResource) Schema(_ context.Context, _ resource.SchemaRequest,
6162
},
6263
},
6364
"policies": schema.ListAttribute{
64-
// TODO: Use `ListUnorderedEqual` to remove requirement for this to be sorted.
65-
Description: "Policy UUIDs to which to apply tag. Sorted by policy name.",
65+
Description: "Policy UUIDs to which to apply tag.",
6666
Required: true,
6767
ElementType: types.StringType,
6868
},
@@ -146,7 +146,7 @@ func (r *tagPoliciesResource) Create(ctx context.Context, req resource.CreateReq
146146
tflog.Debug(ctx, "Created Tag Policies", map[string]any{
147147
"id": plan.ID.ValueString(),
148148
"tag": plan.Tag.ValueString(),
149-
"policies": Map(plan.Policies, func(item types.String) string { return item.ValueString() }),
149+
"policies": Map(plan.Policies, types.String.ValueString),
150150
})
151151
}
152152

@@ -162,7 +162,7 @@ func (r *tagPoliciesResource) Read(ctx context.Context, req resource.ReadRequest
162162
tflog.Debug(ctx, "Reading Tag Policies", map[string]any{
163163
"tag": tagName,
164164
"policies.#": len(state.Policies),
165-
"policies": Map(state.Policies, func(item types.String) string { return item.ValueString() }),
165+
"policies": Map(state.Policies, types.String.ValueString),
166166
})
167167

168168
taggedPoliciesInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedPolicyListResponseItem], error) {
@@ -176,12 +176,20 @@ func (r *tagPoliciesResource) Read(ctx context.Context, req resource.ReadRequest
176176
return
177177
}
178178

179+
statePolicies := Map(taggedPoliciesInfo, func(info dtrack.TaggedPolicyListResponseItem) types.String {
180+
return types.StringValue(info.UUID.String())
181+
})
182+
183+
if SliceUnorderedEqual(statePolicies, state.Policies, func(a, b types.String) int {
184+
return strings.Compare(a.ValueString(), b.ValueString())
185+
}) {
186+
statePolicies = state.Policies
187+
}
188+
179189
state = tagPoliciesResourceModel{
180-
ID: types.StringValue(tagName),
181-
Tag: types.StringValue(tagName),
182-
Policies: Map(taggedPoliciesInfo, func(info dtrack.TaggedPolicyListResponseItem) types.String {
183-
return types.StringValue(info.UUID.String())
184-
}),
190+
ID: types.StringValue(tagName),
191+
Tag: types.StringValue(tagName),
192+
Policies: statePolicies,
185193
}
186194

187195
diags = resp.State.Set(ctx, &state)
@@ -192,7 +200,7 @@ func (r *tagPoliciesResource) Read(ctx context.Context, req resource.ReadRequest
192200
tflog.Debug(ctx, "Read Tag Policies", map[string]any{
193201
"id": state.ID.ValueString(),
194202
"tag": state.Tag.ValueString(),
195-
"policies": Map(state.Policies, func(v types.String) string { return v.ValueString() }),
203+
"policies": Map(state.Policies, types.String.ValueString),
196204
})
197205
}
198206

@@ -209,7 +217,7 @@ func (r *tagPoliciesResource) Update(ctx context.Context, req resource.UpdateReq
209217
"id": plan.ID.ValueString(),
210218
"tag": tagName,
211219
"policies.#": len(plan.Policies),
212-
"policies": Map(plan.Policies, func(item types.String) string { return item.ValueString() }),
220+
"policies": Map(plan.Policies, types.String.ValueString),
213221
})
214222

215223
currentPoliciesInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedPolicyListResponseItem], error) {
@@ -280,7 +288,7 @@ func (r *tagPoliciesResource) Update(ctx context.Context, req resource.UpdateReq
280288
tflog.Debug(ctx, "Updated Tag Policies", map[string]any{
281289
"id": plan.ID.ValueString(),
282290
"tag": plan.Tag.ValueString(),
283-
"policies": Map(plan.Policies, func(t types.String) string { return t.ValueString() }),
291+
"policies": Map(plan.Policies, types.String.ValueString),
284292
})
285293
}
286294

@@ -297,7 +305,7 @@ func (r *tagPoliciesResource) Delete(ctx context.Context, req resource.DeleteReq
297305
"id": state.ID.ValueString(),
298306
"tag": tagName,
299307
"policies.#": len(state.Policies),
300-
"policies": Map(state.Policies, func(item types.String) string { return item.ValueString() }),
308+
"policies": Map(state.Policies, types.String.ValueString),
301309
})
302310

303311
currentPoliciesInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedPolicyListResponseItem], error) {
@@ -324,7 +332,7 @@ func (r *tagPoliciesResource) Delete(ctx context.Context, req resource.DeleteReq
324332
tflog.Debug(ctx, "Deleted Tag Policies", map[string]any{
325333
"id": state.ID.ValueString(),
326334
"tag": tagName,
327-
"policies": Map(state.Policies, func(policy types.String) string { return policy.ValueString() }),
335+
"policies": Map(state.Policies, types.String.ValueString),
328336
})
329337
}
330338

internal/provider/tag_policies_resource_test.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ func TestAccTagPoliciesResource(t *testing.T) {
1212
Steps: []resource.TestStep{
1313
// Create and Read testing.
1414
{
15+
// Use Project to create the Tag, due to `dependencytrack_tag` being 4.13+.
1516
Config: providerConfig + `
1617
resource "dependencytrack_project" "test" {
17-
name = "Tag_Polcies_Resource_Project"
18+
name = "Tag_Policies_Resource_Project"
1819
tags = ["test_tag_policies_tag"]
1920
}
2021
resource "dependencytrack_policy" "test" {
@@ -97,3 +98,83 @@ resource "dependencytrack_tag_policies" "test" {
9798
},
9899
})
99100
}
101+
102+
func TestAccTagPoliciesResourcePoliciesUnordered(t *testing.T) {
103+
resource.Test(t, resource.TestCase{
104+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
105+
Steps: []resource.TestStep{
106+
// Create and Read testing.
107+
{
108+
// Use Project to create the Tag, due to `dependencytrack_tag` being 4.13+.
109+
Config: providerConfig + `
110+
resource "dependencytrack_project" "test" {
111+
name = "Tag_Policies_Resources_Unordered"
112+
tags = ["test_tag_policies_unordered"]
113+
}
114+
resource "dependencytrack_policy" "a" {
115+
name = "A"
116+
operator = "ANY"
117+
violation = "FAIL"
118+
}
119+
resource "dependencytrack_policy" "z" {
120+
name = "z"
121+
operator = "ANY"
122+
violation = "FAIL"
123+
}
124+
resource "dependencytrack_tag_policies" "test" {
125+
tag = "test_tag_policies_unordered"
126+
policies = [
127+
dependencytrack_policy.z.id,
128+
dependencytrack_policy.a.id,
129+
]
130+
depends_on = [dependencytrack_project.test]
131+
}
132+
`,
133+
Check: resource.ComposeAggregateTestCheckFunc(
134+
resource.TestCheckResourceAttr("dependencytrack_tag_policies.test", "policies.#", "2"),
135+
resource.TestCheckResourceAttrPair("dependencytrack_tag_policies.test", "policies.0", "dependencytrack_policy.z", "id"),
136+
resource.TestCheckResourceAttrPair("dependencytrack_tag_policies.test", "policies.1", "dependencytrack_policy.a", "id"),
137+
),
138+
},
139+
// Update and Read testing.
140+
{
141+
Config: providerConfig + `
142+
resource "dependencytrack_project" "test" {
143+
name = "Tag_Policies_Resources_Unordered"
144+
tags = ["test_tag_policies_unordered"]
145+
}
146+
resource "dependencytrack_policy" "a" {
147+
name = "A"
148+
operator = "ANY"
149+
violation = "FAIL"
150+
}
151+
resource "dependencytrack_policy" "z" {
152+
name = "Z"
153+
operator = "ANY"
154+
violation = "FAIL"
155+
}
156+
resource "dependencytrack_policy" "b" {
157+
name = "B"
158+
operator = "ANY"
159+
violation = "FAIL"
160+
}
161+
resource "dependencytrack_tag_policies" "test" {
162+
tag = "test_tag_policies_unordered"
163+
policies = [
164+
dependencytrack_policy.z.id,
165+
dependencytrack_policy.a.id,
166+
dependencytrack_policy.b.id
167+
]
168+
depends_on = [dependencytrack_project.test]
169+
}
170+
`,
171+
Check: resource.ComposeAggregateTestCheckFunc(
172+
resource.TestCheckResourceAttr("dependencytrack_tag_policies.test", "policies.#", "3"),
173+
resource.TestCheckResourceAttrPair("dependencytrack_tag_policies.test", "policies.0", "dependencytrack_policy.z", "id"),
174+
resource.TestCheckResourceAttrPair("dependencytrack_tag_policies.test", "policies.1", "dependencytrack_policy.a", "id"),
175+
resource.TestCheckResourceAttrPair("dependencytrack_tag_policies.test", "policies.2", "dependencytrack_policy.b", "id"),
176+
),
177+
},
178+
},
179+
})
180+
}

internal/provider/tag_projects_resource.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package provider
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
dtrack "github.com/DependencyTrack/client-go"
89
"github.com/google/uuid"
@@ -61,8 +62,7 @@ func (*tagProjectsResource) Schema(_ context.Context, _ resource.SchemaRequest,
6162
},
6263
},
6364
"projects": schema.ListAttribute{
64-
// TODO: Use `ListUnorderedEqual` to remove requirement for this to be sorted.
65-
Description: "Project UUIDs to which to apply tag. Sorted by project name.",
65+
Description: "Project UUIDs to which to apply tag.",
6666
Required: true,
6767
ElementType: types.StringType,
6868
},
@@ -146,7 +146,7 @@ func (r *tagProjectsResource) Create(ctx context.Context, req resource.CreateReq
146146
tflog.Debug(ctx, "Created Tag Projects", map[string]any{
147147
"id": plan.ID.ValueString(),
148148
"tag": plan.Tag.ValueString(),
149-
"projects": Map(plan.Projects, func(item types.String) string { return item.ValueString() }),
149+
"projects": Map(plan.Projects, types.String.ValueString),
150150
})
151151
}
152152

@@ -163,7 +163,7 @@ func (r *tagProjectsResource) Read(ctx context.Context, req resource.ReadRequest
163163
"id": state.ID.ValueString(),
164164
"tag": tagName,
165165
"projects.#": len(state.Projects),
166-
"projects": Map(state.Projects, func(item types.String) string { return item.ValueString() }),
166+
"projects": Map(state.Projects, types.String.ValueString),
167167
})
168168

169169
taggedProjectsInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedProjectListResponseItem], error) {
@@ -177,12 +177,20 @@ func (r *tagProjectsResource) Read(ctx context.Context, req resource.ReadRequest
177177
return
178178
}
179179

180+
stateProjects := Map(taggedProjectsInfo, func(info dtrack.TaggedProjectListResponseItem) types.String {
181+
return types.StringValue(info.UUID.String())
182+
})
183+
184+
if SliceUnorderedEqual(stateProjects, state.Projects, func(a, b types.String) int {
185+
return strings.Compare(a.ValueString(), b.ValueString())
186+
}) {
187+
stateProjects = state.Projects
188+
}
189+
180190
state = tagProjectsResourceModel{
181-
ID: types.StringValue(tagName),
182-
Tag: types.StringValue(tagName),
183-
Projects: Map(taggedProjectsInfo, func(info dtrack.TaggedProjectListResponseItem) types.String {
184-
return types.StringValue(info.UUID.String())
185-
}),
191+
ID: types.StringValue(tagName),
192+
Tag: types.StringValue(tagName),
193+
Projects: stateProjects,
186194
}
187195

188196
diags = resp.State.Set(ctx, &state)
@@ -193,7 +201,7 @@ func (r *tagProjectsResource) Read(ctx context.Context, req resource.ReadRequest
193201
tflog.Debug(ctx, "Read Tag Projects", map[string]any{
194202
"id": state.ID.ValueString(),
195203
"tag": state.Tag.ValueString(),
196-
"projects": Map(state.Projects, func(v types.String) string { return v.ValueString() }),
204+
"projects": Map(state.Projects, types.String.ValueString),
197205
})
198206
}
199207

@@ -210,7 +218,7 @@ func (r *tagProjectsResource) Update(ctx context.Context, req resource.UpdateReq
210218
"id": plan.ID.ValueString(),
211219
"tag": tagName,
212220
"projects.#": len(plan.Projects),
213-
"projects": Map(plan.Projects, func(item types.String) string { return item.ValueString() }),
221+
"projects": Map(plan.Projects, types.String.ValueString),
214222
})
215223

216224
currentProjectsInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedProjectListResponseItem], error) {
@@ -281,7 +289,7 @@ func (r *tagProjectsResource) Update(ctx context.Context, req resource.UpdateReq
281289
tflog.Debug(ctx, "Updated Tag Projects", map[string]any{
282290
"id": plan.ID.ValueString(),
283291
"tag": plan.Tag.ValueString(),
284-
"projects": Map(plan.Projects, func(t types.String) string { return t.ValueString() }),
292+
"projects": Map(plan.Projects, types.String.ValueString),
285293
})
286294
}
287295

@@ -298,7 +306,7 @@ func (r *tagProjectsResource) Delete(ctx context.Context, req resource.DeleteReq
298306
"id": state.ID.ValueString(),
299307
"tag": tagName,
300308
"projects.#": len(state.Projects),
301-
"projects": Map(state.Projects, func(t types.String) string { return t.ValueString() }),
309+
"projects": Map(state.Projects, types.String.ValueString),
302310
})
303311

304312
currentProjectsInfo, err := dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.TaggedProjectListResponseItem], error) {
@@ -323,11 +331,9 @@ func (r *tagProjectsResource) Delete(ctx context.Context, req resource.DeleteReq
323331
return
324332
}
325333
tflog.Debug(ctx, "Deleted Tag Projects", map[string]any{
326-
"id": state.ID.ValueString(),
327-
"tag": tagName,
328-
"projects": Map(state.Projects, func(project types.String) string {
329-
return project.ValueString()
330-
}),
334+
"id": state.ID.ValueString(),
335+
"tag": tagName,
336+
"projects": Map(state.Projects, types.String.ValueString),
331337
})
332338
}
333339

@@ -353,7 +359,7 @@ func (r *tagProjectsResource) Configure(_ context.Context, req resource.Configur
353359
if !ok {
354360
resp.Diagnostics.AddError(
355361
"Unexpected Configure Type",
356-
fmt.Sprintf("Expected provider.clientInfo, got %T. Please report this issue to the provider developer.", req.ProviderData),
362+
fmt.Sprintf("Expected provider.clientDebug, got %T. Please report this issue to the provider developer.", req.ProviderData),
357363
)
358364
return
359365
}

0 commit comments

Comments
 (0)