Skip to content

Commit

Permalink
Add notification_publisher resource
Browse files Browse the repository at this point in the history
  • Loading branch information
majori committed Mar 22, 2024
1 parent df8d9ef commit 9e6f4f2
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 40 deletions.
66 changes: 33 additions & 33 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,43 @@ before:
# this is just an example and not a requirement for provider building/publishing
- go mod tidy
builds:
- env:
# goreleaser does not work with CGO, it could also complicate
# usage by users in CI/CD systems like Terraform Cloud where
# they are unable to install libraries.
- CGO_ENABLED=0
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}'
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- amd64
- '386'
- arm
- arm64
ignore:
- goos: darwin
goarch: '386'
binary: '{{ .ProjectName }}_v{{ .Version }}'
- env:
# goreleaser does not work with CGO, it could also complicate
# usage by users in CI/CD systems like Terraform Cloud where
# they are unable to install libraries.
- CGO_ENABLED=0
mod_timestamp: "{{ .CommitTimestamp }}"
flags:
- -trimpath
ldflags:
- "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}"
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- amd64
- "386"
- arm
- arm64
ignore:
- goos: darwin
goarch: "386"
binary: "{{ .ProjectName }}_v{{ .Version }}"
archives:
- format: zip
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
- format: zip
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
checksum:
extra_files:
- glob: 'terraform-registry-manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
- glob: "terraform-registry-manifest.json"
name_template: "{{ .ProjectName }}_{{ .Version }}_manifest.json"
name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS"
algorithm: sha256
signs:
- artifacts: checksum
args:
# if you are using this in a GitHub action or some other automated pipeline, you
# if you are using this in a GitHub action or some other automated pipeline, you
# need to pass the batch flag to indicate its not interactive.
- "--batch"
- "--local-user"
Expand All @@ -52,9 +52,9 @@ signs:
- "${artifact}"
release:
extra_files:
- glob: 'terraform-registry-manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
- glob: "terraform-registry-manifest.json"
name_template: "{{ .ProjectName }}_{{ .Version }}_manifest.json"
# If you want to manually examine the release before its live, uncomment this line:
# draft: true
changelog:
skip: true
disable: true
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ module github.com/futurice/terraform-provider-dependencytrack
go 1.21

require (
github.com/futurice/dependency-track-client-go v0.0.0-20240320094333-08d36fed58af
github.com/futurice/dependency-track-client-go v0.0.0-20240322102355-485c6eed2e38
github.com/google/uuid v1.6.0
github.com/hashicorp/terraform-plugin-docs v0.18.0
github.com/hashicorp/terraform-plugin-framework v1.6.1
github.com/hashicorp/terraform-plugin-go v0.22.1
)

require (
Expand All @@ -34,6 +33,7 @@ require (
github.com/hashicorp/hc-install v0.6.3 // indirect
github.com/hashicorp/terraform-exec v0.20.0 // indirect
github.com/hashicorp/terraform-json v0.21.0 // indirect
github.com/hashicorp/terraform-plugin-go v0.22.1 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/futurice/dependency-track-client-go v0.0.0-20240319084150-8159a94863d2 h1:wub1QqnHzmoS2MiKEK8iv8mB/7jj5LTlALxejTdeI3w=
github.com/futurice/dependency-track-client-go v0.0.0-20240319084150-8159a94863d2/go.mod h1:nSSUhNjXItvlllTmfE3BdooP1GtAuu6dDXFAHSem0Jk=
github.com/futurice/dependency-track-client-go v0.0.0-20240320094333-08d36fed58af h1:Yhnl7DeretNzRXedYNpjfZWirHy1d/F+hDYRFEt2BE4=
github.com/futurice/dependency-track-client-go v0.0.0-20240320094333-08d36fed58af/go.mod h1:nSSUhNjXItvlllTmfE3BdooP1GtAuu6dDXFAHSem0Jk=
github.com/futurice/dependency-track-client-go v0.0.0-20240322102355-485c6eed2e38 h1:lDP0KV8wctzOqtd1rOkvex9TfecQ9bx/J66n8XCDTaw=
github.com/futurice/dependency-track-client-go v0.0.0-20240322102355-485c6eed2e38/go.mod h1:nSSUhNjXItvlllTmfE3BdooP1GtAuu6dDXFAHSem0Jk=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
Expand Down
247 changes: 247 additions & 0 deletions internal/provider/notification_publisher_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"

dtrack "github.com/futurice/dependency-track-client-go"
"github.com/google/uuid"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &NotificationPublisherResource{}
var _ resource.ResourceWithImportState = &NotificationPublisherResource{}

func NewNotificationPublisherResource() resource.Resource {
return &NotificationPublisherResource{}
}

// NotificationPublisherResource defines the resource implementation.
type NotificationPublisherResource struct {
client *dtrack.Client
}

// NotificationPublisherResourceModel describes the resource data model.
type NotificationPublisherResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
PublisherClass types.String `tfsdk:"publisher_class"`
DefaultPublisher types.Bool `tfsdk:"default_publisher"`
TemplateMimeType types.String `tfsdk:"template_mime_type"`
Template types.String `tfsdk:"template"`
}

func (r *NotificationPublisherResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_notification_publisher"
}

func (r *NotificationPublisherResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Notification publisher",

Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
MarkdownDescription: "Name of the rule",
Required: true,
},
"id": schema.StringAttribute{
MarkdownDescription: "Publisher UUID",
Computed: true,
},
"template_mime_type": schema.StringAttribute{
Required: true,
},
"template": schema.StringAttribute{
Required: true,
},
"publisher_class": schema.StringAttribute{
Required: true,
},
"description": schema.StringAttribute{
Optional: true,
},
"default_publisher": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
},
}
}

func (r *NotificationPublisherResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*dtrack.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *dtrack.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client
}

func (r *NotificationPublisherResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan NotificationPublisherResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

if resp.Diagnostics.HasError() {
return
}

dtPublisher, diags := TFPublisherToDTPublisher(ctx, plan)
resp.Diagnostics.Append(diags...)

respPublisher, err := r.client.Notification.CreatePublisher(ctx, dtPublisher)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create notification publisher, got error: %s", err))
return
}

plan, diags = DTPublisherToTFPublisher(ctx, respPublisher)
resp.Diagnostics.Append(diags...)

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *NotificationPublisherResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state NotificationPublisherResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

publishers, err := r.client.Notification.GetAllPublishers(ctx)
if err != nil {
if apiErr, ok := err.(*dtrack.APIError); ok && apiErr.StatusCode == 404 {
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read notification publisher, got error: %s", err))
return
}

found := false
for _, publisher := range publishers {
if publisher.UUID.String() == state.ID.ValueString() {
found = true
newState, diags := DTPublisherToTFPublisher(ctx, publisher)
resp.Diagnostics.Append(diags...)
state = newState
break
}
}

if !found {
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *NotificationPublisherResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state NotificationPublisherResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

plan.ID = state.ID

dtPublisher, diags := TFPublisherToDTPublisher(ctx, plan)
resp.Diagnostics.Append(diags...)

respPublisher, err := r.client.Notification.UpdatePublisher(ctx, dtPublisher)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update notification publisher, got error: %s", err))
return
}

state, diags = DTPublisherToTFPublisher(ctx, respPublisher)
resp.Diagnostics.Append(diags...)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *NotificationPublisherResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state NotificationPublisherResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

err := r.client.Notification.DeletePublisher(ctx, uuid.MustParse(state.ID.ValueString()))
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete notification publisher, got error: %s", err))
return
}

resp.State.RemoveResource(ctx)
}

func (r *NotificationPublisherResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

func DTPublisherToTFPublisher(ctx context.Context, dtPublisher dtrack.NotificationPublisher) (NotificationPublisherResourceModel, diag.Diagnostics) {
var diags diag.Diagnostics
publisher := NotificationPublisherResourceModel{
ID: types.StringValue(dtPublisher.UUID.String()),
Name: types.StringValue(dtPublisher.Name),
Description: types.StringValue(dtPublisher.Description),
PublisherClass: types.StringValue(dtPublisher.PublisherClass),
DefaultPublisher: types.BoolValue(dtPublisher.DefaultPublisher),
TemplateMimeType: types.StringValue(dtPublisher.TemplateMimeType),
Template: types.StringValue(dtPublisher.Template),
}

return publisher, diags
}

func TFPublisherToDTPublisher(ctx context.Context, tfPublisher NotificationPublisherResourceModel) (dtrack.NotificationPublisher, diag.Diagnostics) {
var diags diag.Diagnostics
publisher := dtrack.NotificationPublisher{
Name: tfPublisher.Name.ValueString(),
Description: tfPublisher.Description.ValueString(),
PublisherClass: tfPublisher.PublisherClass.ValueString(),
DefaultPublisher: tfPublisher.DefaultPublisher.ValueBool(),
TemplateMimeType: tfPublisher.TemplateMimeType.ValueString(),
Template: tfPublisher.Template.ValueString(),
}

if tfPublisher.ID.IsUnknown() {
publisher.UUID = uuid.Nil
} else {
publisher.UUID = uuid.MustParse(tfPublisher.ID.ValueString())
}

return publisher, diags
}
6 changes: 5 additions & 1 deletion internal/provider/notification_rule_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,16 @@ func (r *NotificationRuleResource) Update(ctx context.Context, req resource.Upda
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

plan.ID = state.ID
if plan.PublisherID != state.PublisherID {
resp.Diagnostics.AddError("Client Error", "Publisher can not be changed after creation of the alert. Please recreate the resource")
}

if resp.Diagnostics.HasError() {
return
}

plan.ID = state.ID

dtRule, diags := TFRuleToDTRule(ctx, plan)
resp.Diagnostics.Append(diags...)

Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func (p *DependencyTrackProvider) Resources(ctx context.Context) []func() resour
NewProjectResource,
NewACLMappingResource,
NewNotificationRuleResource,
NewNotificationPublisherResource,
}
}

Expand Down

0 comments on commit 9e6f4f2

Please sign in to comment.