Skip to content

Commit af73838

Browse files
committed
Add notification rule, project/team scoping, and publisher data source
Add support for configuring DependencyTrack notifications via Terraform: - dependencytrack_notification_rule: manages notification rules with full support for event-driven and scheduled triggers, all notification groups (SYSTEM and PORTFOLIO scopes), publisher config, notify_on, message, notify_children, log_successful_publish, schedule_cron, and schedule_skip_unchanged options. - dependencytrack_notification_rule_project: scopes a notification rule to a specific project (PORTFOLIO scope only). - dependencytrack_notification_rule_team: associates a team with a notification rule (for email publisher). - dependencytrack_notification_publisher (data source): looks up a notification publisher by name to retrieve its UUID. Uses SolarFactories/client-go fork with notification API support (PR DependencyTrack/client-go#56). https://claude.ai/code/session_019ocavNKpCndi448shsGs4Q
1 parent 3363a3c commit af73838

15 files changed

Lines changed: 1489 additions & 2 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Look up a built-in notification publisher by name
2+
data "dependencytrack_notification_publisher" "webhook" {
3+
name = "Outbound Webhook"
4+
}
5+
6+
# Other available publishers:
7+
# "Slack", "Microsoft Teams", "Mattermost", "Email",
8+
# "Console", "Cisco Webex", "Jira"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Look up the Outbound Webhook publisher
2+
data "dependencytrack_notification_publisher" "webhook" {
3+
name = "Outbound Webhook"
4+
}
5+
6+
# Event-driven notification rule for new vulnerabilities
7+
resource "dependencytrack_notification_rule" "vuln_webhook" {
8+
name = "Vulnerability Webhook"
9+
scope = "PORTFOLIO"
10+
notification_level = "INFORMATIONAL"
11+
publisher_id = data.dependencytrack_notification_publisher.webhook.id
12+
notify_on = ["NEW_VULNERABILITY", "NEW_VULNERABLE_DEPENDENCY"]
13+
publisher_config = jsonencode({ destination = "https://example.com/webhook" })
14+
}
15+
16+
# Scheduled notification rule for daily vulnerability summary
17+
resource "dependencytrack_notification_rule" "daily_summary" {
18+
name = "Daily Vulnerability Summary"
19+
scope = "PORTFOLIO"
20+
notification_level = "INFORMATIONAL"
21+
trigger_type = "SCHEDULE"
22+
publisher_id = data.dependencytrack_notification_publisher.webhook.id
23+
notify_on = ["NEW_VULNERABILITIES_SUMMARY"]
24+
schedule_cron = "0 8 * * *"
25+
schedule_skip_unchanged = true
26+
publisher_config = jsonencode({ destination = "https://example.com/webhook" })
27+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Scope a notification rule to a specific project
2+
resource "dependencytrack_notification_rule_project" "example" {
3+
rule = dependencytrack_notification_rule.vuln_webhook.id
4+
project = dependencytrack_project.example.id
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Associate a team with an email notification rule
2+
resource "dependencytrack_notification_rule_team" "example" {
3+
rule = dependencytrack_notification_rule.email_alerts.id
4+
team = dependencytrack_team.security.id
5+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ require (
6262
google.golang.org/grpc v1.79.2 // indirect
6363
google.golang.org/protobuf v1.36.11 // indirect
6464
)
65+
66+
replace github.com/DependencyTrack/client-go => github.com/SolarFactories/client-go v0.0.0-20260223220603-b82901019da9

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
22
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
33
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
44
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
5-
github.com/DependencyTrack/client-go v0.18.0 h1:HXOUuqKaxIIiksYzJTM6ltuqebxjUW0kFSbPsGSWX1Q=
6-
github.com/DependencyTrack/client-go v0.18.0/go.mod h1:T5iPG+foFcv6Bn5bTNbjywbmm+gwku4I1MuQWA77sR8=
75
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
86
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
97
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
108
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
9+
github.com/SolarFactories/client-go v0.0.0-20260223220603-b82901019da9 h1:DhpDPxH4DqTYuqeahSfH6mORjXr2OThIeg/Qb8d/4SM=
10+
github.com/SolarFactories/client-go v0.0.0-20260223220603-b82901019da9/go.mod h1:T5iPG+foFcv6Bn5bTNbjywbmm+gwku4I1MuQWA77sR8=
1111
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
1212
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
1313
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
dtrack "github.com/DependencyTrack/client-go"
8+
"github.com/hashicorp/terraform-plugin-framework/datasource"
9+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/types"
11+
"github.com/hashicorp/terraform-plugin-log/tflog"
12+
)
13+
14+
var (
15+
_ datasource.DataSource = &notificationPublisherDataSource{}
16+
_ datasource.DataSourceWithConfigure = &notificationPublisherDataSource{}
17+
)
18+
19+
type (
20+
notificationPublisherDataSource struct {
21+
client *dtrack.Client
22+
semver *Semver
23+
}
24+
25+
notificationPublisherDataSourceModel struct {
26+
ID types.String `tfsdk:"id"`
27+
Name types.String `tfsdk:"name"`
28+
Description types.String `tfsdk:"description"`
29+
PublisherClass types.String `tfsdk:"publisher_class"`
30+
DefaultPublisher types.Bool `tfsdk:"default_publisher"`
31+
}
32+
)
33+
34+
func NewNotificationPublisherDataSource() datasource.DataSource {
35+
return &notificationPublisherDataSource{}
36+
}
37+
38+
func (*notificationPublisherDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
39+
resp.TypeName = req.ProviderTypeName + "_notification_publisher"
40+
}
41+
42+
func (*notificationPublisherDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
43+
resp.Schema = schema.Schema{
44+
Description: "Fetches a Notification Publisher by name.",
45+
Attributes: map[string]schema.Attribute{
46+
"name": schema.StringAttribute{
47+
Description: "Name of the Notification Publisher to look up. " +
48+
"Built-in publishers include: Slack, Microsoft Teams, Mattermost, Email, Console, Outbound Webhook, Cisco Webex, Jira.",
49+
Required: true,
50+
},
51+
"id": schema.StringAttribute{
52+
Description: "UUID of the Notification Publisher.",
53+
Computed: true,
54+
},
55+
"description": schema.StringAttribute{
56+
Description: "Description of the Notification Publisher.",
57+
Computed: true,
58+
},
59+
"publisher_class": schema.StringAttribute{
60+
Description: "Fully-qualified class name of the publisher implementation.",
61+
Computed: true,
62+
},
63+
"default_publisher": schema.BoolAttribute{
64+
Description: "Whether this is a default built-in publisher.",
65+
Computed: true,
66+
},
67+
},
68+
}
69+
}
70+
71+
func (d *notificationPublisherDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
72+
var state notificationPublisherDataSourceModel
73+
diags := req.Config.Get(ctx, &state)
74+
resp.Diagnostics.Append(diags...)
75+
if resp.Diagnostics.HasError() {
76+
return
77+
}
78+
79+
name := state.Name.ValueString()
80+
tflog.Debug(ctx, "Reading Notification Publisher", map[string]any{
81+
"name": name,
82+
})
83+
84+
publishers, err := d.client.Notification.GetAllPublishers(ctx)
85+
if err != nil {
86+
resp.Diagnostics.AddError(
87+
"Unable to fetch notification publishers",
88+
"Error from: "+err.Error(),
89+
)
90+
return
91+
}
92+
93+
publisher, err := Find(publishers, func(p dtrack.NotificationPublisher) bool {
94+
return p.Name == name
95+
})
96+
if err != nil {
97+
resp.Diagnostics.AddError(
98+
"Unable to find notification publisher",
99+
fmt.Sprintf("No publisher found with name '%s'. Error: %s", name, err.Error()),
100+
)
101+
return
102+
}
103+
104+
state = notificationPublisherDataSourceModel{
105+
ID: types.StringValue(publisher.UUID.String()),
106+
Name: types.StringValue(publisher.Name),
107+
Description: types.StringValue(publisher.Description),
108+
PublisherClass: types.StringValue(publisher.PublisherClass),
109+
DefaultPublisher: types.BoolValue(publisher.DefaultPublisher),
110+
}
111+
112+
diags = resp.State.Set(ctx, &state)
113+
resp.Diagnostics.Append(diags...)
114+
if resp.Diagnostics.HasError() {
115+
return
116+
}
117+
tflog.Debug(ctx, "Read Notification Publisher", map[string]any{
118+
"id": state.ID.ValueString(),
119+
"name": state.Name.ValueString(),
120+
})
121+
}
122+
123+
func (d *notificationPublisherDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
124+
if req.ProviderData == nil {
125+
return
126+
}
127+
clientInfoData, ok := req.ProviderData.(clientInfo)
128+
129+
if !ok {
130+
resp.Diagnostics.AddError(
131+
"Unexpected Configure Type",
132+
fmt.Sprintf("Expected provider.clientInfo, got %T. Please report this issue to the provider developers.", req.ProviderData),
133+
)
134+
return
135+
}
136+
d.client = clientInfoData.client
137+
d.semver = clientInfoData.semver
138+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package provider
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
)
8+
9+
func TestAccNotificationPublisherDataSource(t *testing.T) {
10+
resource.Test(t, resource.TestCase{
11+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
12+
Steps: []resource.TestStep{
13+
{
14+
Config: providerConfig + `
15+
data "dependencytrack_notification_publisher" "webhook" {
16+
name = "Outbound Webhook"
17+
}
18+
`,
19+
Check: resource.ComposeAggregateTestCheckFunc(
20+
resource.TestCheckResourceAttrSet("data.dependencytrack_notification_publisher.webhook", "id"),
21+
resource.TestCheckResourceAttr("data.dependencytrack_notification_publisher.webhook", "name", "Outbound Webhook"),
22+
resource.TestCheckResourceAttrSet("data.dependencytrack_notification_publisher.webhook", "publisher_class"),
23+
resource.TestCheckResourceAttr("data.dependencytrack_notification_publisher.webhook", "default_publisher", "true"),
24+
),
25+
},
26+
},
27+
})
28+
}

0 commit comments

Comments
 (0)