Skip to content

Commit 8f46c10

Browse files
committed
Add dependencytrack_notification_publisher resource
Implements CRUD operations for managing notification publishers, including create, read, update, delete, and import support. Registers the new resource in the provider. https://claude.ai/code/session_019ocavNKpCndi448shsGs4Q
1 parent eb9ac2b commit 8f46c10

3 files changed

Lines changed: 384 additions & 0 deletions

File tree

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
dtrack "github.com/DependencyTrack/client-go"
8+
"github.com/google/uuid"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
15+
"github.com/hashicorp/terraform-plugin-log/tflog"
16+
)
17+
18+
var (
19+
_ resource.Resource = &notificationPublisherResource{}
20+
_ resource.ResourceWithConfigure = &notificationPublisherResource{}
21+
_ resource.ResourceWithImportState = &notificationPublisherResource{}
22+
)
23+
24+
type (
25+
notificationPublisherResource struct {
26+
client *dtrack.Client
27+
semver *Semver
28+
}
29+
30+
notificationPublisherResourceModel struct {
31+
ID types.String `tfsdk:"id"`
32+
Name types.String `tfsdk:"name"`
33+
Description types.String `tfsdk:"description"`
34+
PublisherClass types.String `tfsdk:"publisher_class"`
35+
Template types.String `tfsdk:"template"`
36+
TemplateMIMEType types.String `tfsdk:"template_mime_type"`
37+
DefaultPublisher types.Bool `tfsdk:"default_publisher"`
38+
}
39+
)
40+
41+
func NewNotificationPublisherResource() resource.Resource {
42+
return &notificationPublisherResource{}
43+
}
44+
45+
func (*notificationPublisherResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
46+
resp.TypeName = req.ProviderTypeName + "_notification_publisher"
47+
}
48+
49+
func (*notificationPublisherResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
50+
resp.Schema = schema.Schema{
51+
Description: "Manages a Notification Publisher.",
52+
Attributes: map[string]schema.Attribute{
53+
"id": schema.StringAttribute{
54+
Description: "UUID for the Notification Publisher as generated by DependencyTrack.",
55+
Computed: true,
56+
PlanModifiers: []planmodifier.String{
57+
stringplanmodifier.UseStateForUnknown(),
58+
},
59+
},
60+
"name": schema.StringAttribute{
61+
Description: "Name of the Notification Publisher.",
62+
Required: true,
63+
},
64+
"description": schema.StringAttribute{
65+
Description: "Description of the Notification Publisher.",
66+
Optional: true,
67+
},
68+
"publisher_class": schema.StringAttribute{
69+
Description: "Fully-qualified class name of the publisher implementation.",
70+
Required: true,
71+
PlanModifiers: []planmodifier.String{
72+
stringplanmodifier.RequiresReplace(),
73+
},
74+
},
75+
"template": schema.StringAttribute{
76+
Description: "Template content for the publisher.",
77+
Optional: true,
78+
},
79+
"template_mime_type": schema.StringAttribute{
80+
Description: "MIME type of the template (e.g. 'application/json').",
81+
Required: true,
82+
},
83+
"default_publisher": schema.BoolAttribute{
84+
Description: "Whether this is a default built-in publisher.",
85+
Computed: true,
86+
},
87+
},
88+
}
89+
}
90+
91+
func (r *notificationPublisherResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
92+
var plan notificationPublisherResourceModel
93+
diags := req.Plan.Get(ctx, &plan)
94+
resp.Diagnostics.Append(diags...)
95+
if resp.Diagnostics.HasError() {
96+
return
97+
}
98+
99+
tflog.Debug(ctx, "Creating Notification Publisher", map[string]any{
100+
"name": plan.Name.ValueString(),
101+
})
102+
103+
publisher := dtrack.NotificationPublisher{
104+
Name: plan.Name.ValueString(),
105+
PublisherClass: plan.PublisherClass.ValueString(),
106+
TemplateMIMEType: plan.TemplateMIMEType.ValueString(),
107+
}
108+
if !plan.Description.IsNull() && !plan.Description.IsUnknown() {
109+
publisher.Description = plan.Description.ValueString()
110+
}
111+
if !plan.Template.IsNull() && !plan.Template.IsUnknown() {
112+
publisher.Template = plan.Template.ValueString()
113+
}
114+
115+
publisherRes, err := r.client.Notification.CreatePublisher(ctx, publisher)
116+
if err != nil {
117+
resp.Diagnostics.AddError(
118+
"Error creating notification publisher",
119+
"Error from: "+err.Error(),
120+
)
121+
return
122+
}
123+
124+
r.mapToState(&plan, publisherRes)
125+
126+
diags = resp.State.Set(ctx, plan)
127+
resp.Diagnostics.Append(diags...)
128+
if resp.Diagnostics.HasError() {
129+
return
130+
}
131+
tflog.Debug(ctx, "Created Notification Publisher", map[string]any{
132+
"id": plan.ID.ValueString(),
133+
"name": plan.Name.ValueString(),
134+
})
135+
}
136+
137+
func (r *notificationPublisherResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
138+
var state notificationPublisherResourceModel
139+
diags := req.State.Get(ctx, &state)
140+
resp.Diagnostics.Append(diags...)
141+
if resp.Diagnostics.HasError() {
142+
return
143+
}
144+
145+
publisherID, diag := TryParseUUID(state.ID, LifecycleRead, path.Root("id"))
146+
if diag != nil {
147+
resp.Diagnostics.Append(diag)
148+
return
149+
}
150+
151+
tflog.Debug(ctx, "Reading Notification Publisher", map[string]any{
152+
"id": publisherID.String(),
153+
})
154+
155+
publisher, err := r.findPublisher(ctx, publisherID)
156+
if err != nil {
157+
resp.Diagnostics.AddError(
158+
"Unable to read notification publisher",
159+
"Error from: "+err.Error(),
160+
)
161+
return
162+
}
163+
164+
r.mapToState(&state, *publisher)
165+
166+
diags = resp.State.Set(ctx, &state)
167+
resp.Diagnostics.Append(diags...)
168+
if resp.Diagnostics.HasError() {
169+
return
170+
}
171+
tflog.Debug(ctx, "Read Notification Publisher", map[string]any{
172+
"id": state.ID.ValueString(),
173+
"name": state.Name.ValueString(),
174+
})
175+
}
176+
177+
func (r *notificationPublisherResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
178+
var plan notificationPublisherResourceModel
179+
diags := req.Plan.Get(ctx, &plan)
180+
resp.Diagnostics.Append(diags...)
181+
if resp.Diagnostics.HasError() {
182+
return
183+
}
184+
185+
publisherID, diag := TryParseUUID(plan.ID, LifecycleUpdate, path.Root("id"))
186+
if diag != nil {
187+
resp.Diagnostics.Append(diag)
188+
return
189+
}
190+
191+
updateReq := dtrack.NotificationPublisher{
192+
UUID: publisherID,
193+
Name: plan.Name.ValueString(),
194+
PublisherClass: plan.PublisherClass.ValueString(),
195+
TemplateMIMEType: plan.TemplateMIMEType.ValueString(),
196+
}
197+
if !plan.Description.IsNull() && !plan.Description.IsUnknown() {
198+
updateReq.Description = plan.Description.ValueString()
199+
}
200+
if !plan.Template.IsNull() && !plan.Template.IsUnknown() {
201+
updateReq.Template = plan.Template.ValueString()
202+
}
203+
204+
tflog.Debug(ctx, "Updating Notification Publisher", map[string]any{
205+
"id": publisherID.String(),
206+
"name": updateReq.Name,
207+
})
208+
209+
publisherRes, err := r.client.Notification.UpdatePublisher(ctx, updateReq)
210+
if err != nil {
211+
resp.Diagnostics.AddError(
212+
"Unable to update notification publisher",
213+
"Error from: "+err.Error(),
214+
)
215+
return
216+
}
217+
218+
r.mapToState(&plan, publisherRes)
219+
220+
diags = resp.State.Set(ctx, plan)
221+
resp.Diagnostics.Append(diags...)
222+
if resp.Diagnostics.HasError() {
223+
return
224+
}
225+
tflog.Debug(ctx, "Updated Notification Publisher", map[string]any{
226+
"id": plan.ID.ValueString(),
227+
"name": plan.Name.ValueString(),
228+
})
229+
}
230+
231+
func (r *notificationPublisherResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
232+
var state notificationPublisherResourceModel
233+
diags := req.State.Get(ctx, &state)
234+
resp.Diagnostics.Append(diags...)
235+
if resp.Diagnostics.HasError() {
236+
return
237+
}
238+
239+
publisherID, diag := TryParseUUID(state.ID, LifecycleDelete, path.Root("id"))
240+
if diag != nil {
241+
resp.Diagnostics.Append(diag)
242+
return
243+
}
244+
245+
tflog.Debug(ctx, "Deleting Notification Publisher", map[string]any{
246+
"id": publisherID.String(),
247+
"name": state.Name.ValueString(),
248+
})
249+
250+
err := r.client.Notification.DeletePublisher(ctx, publisherID)
251+
if err != nil {
252+
resp.Diagnostics.AddError(
253+
"Unable to delete notification publisher",
254+
"Error from: "+err.Error(),
255+
)
256+
return
257+
}
258+
tflog.Debug(ctx, "Deleted Notification Publisher", map[string]any{
259+
"id": state.ID.ValueString(),
260+
"name": state.Name.ValueString(),
261+
})
262+
}
263+
264+
func (*notificationPublisherResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
265+
tflog.Debug(ctx, "Importing Notification Publisher", map[string]any{
266+
"id": req.ID,
267+
})
268+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
269+
if resp.Diagnostics.HasError() {
270+
return
271+
}
272+
tflog.Debug(ctx, "Imported Notification Publisher", map[string]any{
273+
"id": req.ID,
274+
})
275+
}
276+
277+
func (r *notificationPublisherResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
278+
if req.ProviderData == nil {
279+
return
280+
}
281+
clientInfoData, ok := req.ProviderData.(clientInfo)
282+
283+
if !ok {
284+
resp.Diagnostics.AddError(
285+
"Unexpected Configure Type",
286+
fmt.Sprintf("Expected provider.clientInfo, got %T. Please report this issue to the provider developers.", req.ProviderData),
287+
)
288+
return
289+
}
290+
r.client = clientInfoData.client
291+
r.semver = clientInfoData.semver
292+
}
293+
294+
func (r *notificationPublisherResource) findPublisher(ctx context.Context, publisherID uuid.UUID) (*dtrack.NotificationPublisher, error) {
295+
publishers, err := r.client.Notification.GetAllPublishers(ctx)
296+
if err != nil {
297+
return nil, err
298+
}
299+
300+
return Find(publishers, func(pub dtrack.NotificationPublisher) bool {
301+
return pub.UUID == publisherID
302+
})
303+
}
304+
305+
func (*notificationPublisherResource) mapToState(state *notificationPublisherResourceModel, publisher dtrack.NotificationPublisher) {
306+
state.ID = types.StringValue(publisher.UUID.String())
307+
state.Name = types.StringValue(publisher.Name)
308+
state.PublisherClass = types.StringValue(publisher.PublisherClass)
309+
state.TemplateMIMEType = types.StringValue(publisher.TemplateMIMEType)
310+
state.DefaultPublisher = types.BoolValue(publisher.DefaultPublisher)
311+
312+
if publisher.Description != "" {
313+
state.Description = types.StringValue(publisher.Description)
314+
} else if !state.Description.IsNull() {
315+
state.Description = types.StringNull()
316+
}
317+
318+
if publisher.Template != "" {
319+
state.Template = types.StringValue(publisher.Template)
320+
} else if !state.Template.IsNull() {
321+
state.Template = types.StringNull()
322+
}
323+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package provider
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
)
8+
9+
func TestAccNotificationPublisherResource(t *testing.T) {
10+
resource.Test(t, resource.TestCase{
11+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
12+
Steps: []resource.TestStep{
13+
// Create and Read testing.
14+
{
15+
Config: providerConfig + `
16+
resource "dependencytrack_notification_publisher" "test" {
17+
name = "Test_Publisher"
18+
description = "A test publisher"
19+
publisher_class = "org.dependencytrack.notification.publisher.WebhookPublisher"
20+
template = "{}"
21+
template_mime_type = "application/json"
22+
}
23+
`,
24+
Check: resource.ComposeAggregateTestCheckFunc(
25+
resource.TestCheckResourceAttrSet("dependencytrack_notification_publisher.test", "id"),
26+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "name", "Test_Publisher"),
27+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "description", "A test publisher"),
28+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "publisher_class", "org.dependencytrack.notification.publisher.WebhookPublisher"),
29+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "template", "{}"),
30+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "template_mime_type", "application/json"),
31+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "default_publisher", "false"),
32+
),
33+
},
34+
// ImportState testing.
35+
{
36+
ResourceName: "dependencytrack_notification_publisher.test",
37+
ImportState: true,
38+
ImportStateVerify: true,
39+
},
40+
// Update and Read testing.
41+
{
42+
Config: providerConfig + `
43+
resource "dependencytrack_notification_publisher" "test" {
44+
name = "Test_Publisher_Updated"
45+
description = "An updated test publisher"
46+
publisher_class = "org.dependencytrack.notification.publisher.WebhookPublisher"
47+
template = "{\"updated\": true}"
48+
template_mime_type = "application/json"
49+
}
50+
`,
51+
Check: resource.ComposeAggregateTestCheckFunc(
52+
resource.TestCheckResourceAttrSet("dependencytrack_notification_publisher.test", "id"),
53+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "name", "Test_Publisher_Updated"),
54+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "description", "An updated test publisher"),
55+
resource.TestCheckResourceAttr("dependencytrack_notification_publisher.test", "template", "{\"updated\": true}"),
56+
),
57+
},
58+
},
59+
})
60+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ func (*dependencyTrackProvider) Resources(_ context.Context) []func() resource.R
237237
NewNotificationRuleResource,
238238
NewNotificationRuleProjectResource,
239239
NewNotificationRuleTeamResource,
240+
NewNotificationPublisherResource,
240241
}
241242
}
242243

0 commit comments

Comments
 (0)