diff --git a/docs/resources/notification_synology_indexer.md b/docs/resources/notification_synology_indexer.md new file mode 100644 index 00000000..24856bcd --- /dev/null +++ b/docs/resources/notification_synology_indexer.md @@ -0,0 +1,65 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_synology_indexer Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Synology Indexer resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Synology https://wiki.servarr.com/radarr/supported#synologyindexer. +--- + +# radarr_notification_synology_indexer (Resource) + +Notification Synology Indexer resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Synology](https://wiki.servarr.com/radarr/supported#synologyindexer). + +## Example Usage + +```terraform +resource "radarr_notification_synology_indexer" "example" { + on_download = true + on_upgrade = true + on_rename = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + + include_health_warnings = false + name = "Example" + + update_library = true +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationSynology name. +- `on_download` (Boolean) On download flag. +- `on_movie_added` (Boolean) On movie added flag. +- `on_movie_delete` (Boolean) On movie delete flag. +- `on_movie_file_delete` (Boolean) On movie file delete flag. +- `on_movie_file_delete_for_upgrade` (Boolean) On movie file delete for upgrade flag. +- `on_rename` (Boolean) On rename flag. +- `on_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `tags` (Set of Number) List of associated tags. +- `update_library` (Boolean) Update library flag. + +### Read-Only + +- `id` (Number) Notification ID. + +## Import + +Import is supported using the following syntax: + +```shell +# import using the API/UI ID +terraform import radarr_notification_synology_indexer.example 1 +``` diff --git a/examples/resources/radarr_notification_synology_indexer/import.sh b/examples/resources/radarr_notification_synology_indexer/import.sh new file mode 100644 index 00000000..1baa3eec --- /dev/null +++ b/examples/resources/radarr_notification_synology_indexer/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_synology_indexer.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_synology_indexer/resource.tf b/examples/resources/radarr_notification_synology_indexer/resource.tf new file mode 100644 index 00000000..a1c2c43c --- /dev/null +++ b/examples/resources/radarr_notification_synology_indexer/resource.tf @@ -0,0 +1,14 @@ +resource "radarr_notification_synology_indexer" "example" { + on_download = true + on_upgrade = true + on_rename = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + + include_health_warnings = false + name = "Example" + + update_library = true +} \ No newline at end of file diff --git a/internal/provider/notification_synology_indexer_resource.go b/internal/provider/notification_synology_indexer_resource.go new file mode 100644 index 00000000..1e7a29f7 --- /dev/null +++ b/internal/provider/notification_synology_indexer_resource.go @@ -0,0 +1,328 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "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/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "golift.io/starr/radarr" +) + +const ( + notificationSynologyResourceName = "notification_synology_indexer" + NotificationSynologyImplementation = "SynologyIndexer" + NotificationSynologyConfigContrat = "SynologyIndexerSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationSynologyResource{} +var _ resource.ResourceWithImportState = &NotificationSynologyResource{} + +func NewNotificationSynologyResource() resource.Resource { + return &NotificationSynologyResource{} +} + +// NotificationSynologyResource defines the notification implementation. +type NotificationSynologyResource struct { + client *radarr.Radarr +} + +// NotificationSynology describes the notification data model. +type NotificationSynology struct { + Tags types.Set `tfsdk:"tags"` + Name types.String `tfsdk:"name"` + ID types.Int64 `tfsdk:"id"` + UpdateLibrary types.Bool `tfsdk:"update_library"` + OnMovieFileDeleteForUpgrade types.Bool `tfsdk:"on_movie_file_delete_for_upgrade"` + OnMovieFileDelete types.Bool `tfsdk:"on_movie_file_delete"` + OnMovieAdded types.Bool `tfsdk:"on_movie_added"` + IncludeHealthWarnings types.Bool `tfsdk:"include_health_warnings"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnRename types.Bool `tfsdk:"on_rename"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationSynology) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Name: n.Name, + ID: n.ID, + UpdateLibrary: n.UpdateLibrary, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnMovieDelete: n.OnMovieDelete, + OnRename: n.OnRename, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationSynology) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Name = notification.Name + n.ID = notification.ID + n.UpdateLibrary = notification.UpdateLibrary + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnRename = notification.OnRename + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationSynologyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationSynologyResourceName +} + +func (r *NotificationSynologyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Synology Indexer resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Synology](https://wiki.servarr.com/radarr/supported#synologyindexer).", + Attributes: map[string]schema.Attribute{ + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade flag.", + Required: true, + }, + "on_rename": schema.BoolAttribute{ + MarkdownDescription: "On rename flag.", + Required: true, + }, + "on_movie_added": schema.BoolAttribute{ + MarkdownDescription: "On movie added flag.", + Required: true, + }, + "on_movie_delete": schema.BoolAttribute{ + MarkdownDescription: "On movie delete flag.", + Required: true, + }, + "on_movie_file_delete": schema.BoolAttribute{ + MarkdownDescription: "On movie file delete flag.", + Required: true, + }, + "on_movie_file_delete_for_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On movie file delete for upgrade flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationSynology name.", + Required: true, + }, + "tags": schema.SetAttribute{ + MarkdownDescription: "List of associated tags.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + "id": schema.Int64Attribute{ + MarkdownDescription: "Notification ID.", + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + // Field values + "update_library": schema.BoolAttribute{ + MarkdownDescription: "Update library flag.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (r *NotificationSynologyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*radarr.Radarr) + if !ok { + resp.Diagnostics.AddError( + tools.UnexpectedResourceConfigureType, + fmt.Sprintf("Expected *radarr.Radarr, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *NotificationSynologyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationSynology + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationSynology + request := notification.read(ctx) + + response, err := r.client.AddNotificationContext(ctx, request) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to create %s, got error: %s", notificationSynologyResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationSynologyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSynologyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationSynology + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationSynology current value + response, err := r.client.GetNotificationContext(ctx, int(notification.ID.ValueInt64())) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to read %s, got error: %s", notificationSynologyResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationSynologyResourceName+": "+strconv.Itoa(int(response.ID))) + // Map response body to resource schema attribute + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSynologyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationSynology + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationSynology + request := notification.read(ctx) + + response, err := r.client.UpdateNotificationContext(ctx, request) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to update %s, got error: %s", notificationSynologyResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationSynologyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSynologyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationSynology + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationSynology current value + err := r.client.DeleteNotificationContext(ctx, notification.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to read %s, got error: %s", notificationSynologyResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationSynologyResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationSynologyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + id, err := strconv.Atoi(req.ID) + if err != nil { + resp.Diagnostics.AddError( + tools.UnexpectedImportIdentifier, + fmt.Sprintf("Expected import identifier with format: ID. Got: %q", req.ID), + ) + + return + } + + tflog.Trace(ctx, "imported "+notificationSynologyResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationSynology) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + OnRename: types.BoolValue(notification.OnRename), + OnMovieAdded: types.BoolValue(notification.OnMovieAdded), + OnMovieDelete: types.BoolValue(notification.OnMovieDelete), + OnMovieFileDelete: types.BoolValue(notification.OnMovieFileDelete), + OnMovieFileDeleteForUpgrade: types.BoolValue(notification.OnMovieFileDeleteForUpgrade), + OnHealthIssue: types.BoolValue(notification.OnHealthIssue), + OnApplicationUpdate: types.BoolValue(notification.OnApplicationUpdate), + IncludeHealthWarnings: types.BoolValue(notification.IncludeHealthWarnings), + ID: types.Int64Value(notification.ID), + Name: types.StringValue(notification.Name), + } + genericNotification.Tags, _ = types.SetValueFrom(ctx, types.Int64Type, notification.Tags) + genericNotification.writeFields(ctx, notification.Fields) + n.fromNotification(&genericNotification) +} + +func (n *NotificationSynology) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnRename: n.OnRename.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationSynologyConfigContrat, + Implementation: NotificationSynologyImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_synology_indexer_resource_test.go b/internal/provider/notification_synology_indexer_resource_test.go new file mode 100644 index 00000000..b8decddb --- /dev/null +++ b/internal/provider/notification_synology_indexer_resource_test.go @@ -0,0 +1,59 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationSynologyResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationSynologyResourceConfig("resourceSynologyTest", "false"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_synology_indexer.test", "update_library", "false"), + resource.TestCheckResourceAttrSet("radarr_notification_synology_indexer.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationSynologyResourceConfig("resourceSynologyTest", "true"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_synology_indexer.test", "update_library", "true"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_synology_indexer.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationSynologyResourceConfig(name, update string) string { + return fmt.Sprintf(` + resource "radarr_notification_synology_indexer" "test" { + on_download = false + on_upgrade = false + on_rename = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + + include_health_warnings = false + name = "%s" + + update_library = %s + }`, name, update) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2ec4ad78..ded44f06 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -189,6 +189,7 @@ func (p *RadarrProvider) Resources(ctx context.Context) []func() resource.Resour NewNotificationPushoverResource, NewNotificationSendgridResource, NewNotificationSlackResource, + NewNotificationSynologyResource, NewNotificationWebhookResource, // Profiles