diff --git a/docs/data-sources/notification.md b/docs/data-sources/notification.md index 04a809d6..692752c8 100644 --- a/docs/data-sources/notification.md +++ b/docs/data-sources/notification.md @@ -37,9 +37,9 @@ data "radarr_notification" "example" { - `auth_user` (String) Auth user. - `author` (String) Author. - `avatar` (String) Avatar. -- `bcc` (String) Bcc. +- `bcc` (Set of String) Bcc. - `bot_token` (String) Bot token. -- `cc` (String) Cc. +- `cc` (Set of String) Cc. - `channel` (String) Channel. - `channel_tags` (Set of String) Channel tags. - `chat_id` (String) Chat ID. @@ -48,22 +48,22 @@ data "radarr_notification" "example" { - `config_contract` (String) Notification configuration template. - `consumer_key` (String) Consumer key. - `consumer_secret` (String) Consumer secret. -- `device_ids` (Set of Number) Device IDs. +- `device_ids` (Set of String) Device IDs. - `device_names` (String) Device names. - `devices` (Set of String) Devices. - `direct_message` (Boolean) Direct message flag. -- `display_time` (String) Display time. +- `display_time` (Number) Display time. - `event` (String) Event. - `expire` (Number) Expire. - `expires` (String) Expires. -- `field_tags` (Set of String) Devices. +- `field_tags` (Set of String) Specific tags. - `from` (String) From. -- `grab_fields` (Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore. +- `grab_fields` (Set of Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore. - `host` (String) Host. - `icon` (String) Icon. - `id` (Number) Notification ID. - `implementation` (String) Notification implementation name. -- `import_fields` (Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. +- `import_fields` (Set of Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. - `include_health_warnings` (Boolean) Include health warnings. - `instance_name` (String) Instance name. - `key` (String) Key. @@ -98,9 +98,9 @@ data "radarr_notification" "example" { - `sign_in` (String) Sign in. - `sound` (String) Sound. - `tags` (Set of Number) List of associated tags. -- `to` (String) To. +- `to` (Set of String) To. - `token` (String) Token. -- `topics` (Set of String) Devices. +- `topics` (Set of String) Topics. - `update_library` (Boolean) Update library flag. - `url` (String) URL. - `use_eu_endpoint` (Boolean) Use EU endpoint flag. diff --git a/docs/data-sources/notifications.md b/docs/data-sources/notifications.md index c4d34013..a9f2c468 100644 --- a/docs/data-sources/notifications.md +++ b/docs/data-sources/notifications.md @@ -40,9 +40,9 @@ Read-Only: - `auth_user` (String) Auth user. - `author` (String) Author. - `avatar` (String) Avatar. -- `bcc` (String) Bcc. +- `bcc` (Set of String) Bcc. - `bot_token` (String) Bot token. -- `cc` (String) Cc. +- `cc` (Set of String) Cc. - `channel` (String) Channel. - `channel_tags` (Set of String) Channel tags. - `chat_id` (String) Chat ID. @@ -51,22 +51,22 @@ Read-Only: - `config_contract` (String) Notification configuration template. - `consumer_key` (String) Consumer key. - `consumer_secret` (String) Consumer secret. -- `device_ids` (Set of Number) Device IDs. +- `device_ids` (Set of String) Device IDs. - `device_names` (String) Device names. - `devices` (Set of String) Devices. - `direct_message` (Boolean) Direct message flag. -- `display_time` (String) Display time. +- `display_time` (Number) Display time. - `event` (String) Event. - `expire` (Number) Expire. - `expires` (String) Expires. -- `field_tags` (Set of String) Devices. +- `field_tags` (Set of String) Specific tags. - `from` (String) From. -- `grab_fields` (Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore. +- `grab_fields` (Set of Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore. - `host` (String) Host. - `icon` (String) Icon. - `id` (Number) Notification ID. - `implementation` (String) Notification implementation name. -- `import_fields` (Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. +- `import_fields` (Set of Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. - `include_health_warnings` (Boolean) Include health warnings. - `instance_name` (String) Instance name. - `key` (String) Key. @@ -102,9 +102,9 @@ Read-Only: - `sign_in` (String) Sign in. - `sound` (String) Sound. - `tags` (Set of Number) List of associated tags. -- `to` (String) To. +- `to` (Set of String) To. - `token` (String) Token. -- `topics` (Set of String) Devices. +- `topics` (Set of String) Topics. - `update_library` (Boolean) Update library flag. - `url` (String) URL. - `use_eu_endpoint` (Boolean) Use EU endpoint flag. diff --git a/docs/resources/notification.md b/docs/resources/notification.md index f6fe8030..9ccbcdc9 100644 --- a/docs/resources/notification.md +++ b/docs/resources/notification.md @@ -69,9 +69,9 @@ resource "radarr_notification" "example" { - `auth_user` (String) Auth user. - `author` (String) Author. - `avatar` (String) Avatar. -- `bcc` (String) Bcc. +- `bcc` (Set of String) Bcc. - `bot_token` (String) Bot token. -- `cc` (String) Cc. +- `cc` (Set of String) Cc. - `channel` (String) Channel. - `channel_tags` (Set of String) Channel tags. - `chat_id` (String) Chat ID. @@ -79,20 +79,20 @@ resource "radarr_notification" "example" { - `click_url` (String) Click URL. - `consumer_key` (String) Consumer key. - `consumer_secret` (String) Consumer secret. -- `device_ids` (Set of Number) Device IDs. +- `device_ids` (Set of String) Device IDs. - `device_names` (String) Device names. - `devices` (Set of String) Devices. - `direct_message` (Boolean) Direct message flag. -- `display_time` (String) Display time. +- `display_time` (Number) Display time. - `event` (String) Event. - `expire` (Number) Expire. - `expires` (String) Expires. -- `field_tags` (Set of String) Devices. +- `field_tags` (Set of String) Specific tags. - `from` (String) From. -- `grab_fields` (Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore. +- `grab_fields` (Set of Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart. - `host` (String) Host. - `icon` (String) Icon. -- `import_fields` (Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. +- `import_fields` (Set of Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. - `instance_name` (String) Instance name. - `key` (String) Key. - `map_from` (String) Map From. @@ -116,9 +116,9 @@ resource "radarr_notification" "example" { - `sign_in` (String) Sign in. - `sound` (String) Sound. - `tags` (Set of Number) List of associated tags. -- `to` (String) To. +- `to` (Set of String) To. - `token` (String) Token. -- `topics` (Set of String) Devices. +- `topics` (Set of String) Topics. - `update_library` (Boolean) Update library flag. - `url` (String) URL. - `use_eu_endpoint` (Boolean) Use EU endpoint flag. diff --git a/docs/resources/notification_boxcar.md b/docs/resources/notification_boxcar.md new file mode 100644 index 00000000..7ff6ee07 --- /dev/null +++ b/docs/resources/notification_boxcar.md @@ -0,0 +1,69 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_boxcar Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Boxcar resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Boxcar https://wiki.servarr.com/radarr/supported#boxcar. +--- + +# radarr_notification_boxcar (Resource) + +Notification Boxcar resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Boxcar](https://wiki.servarr.com/radarr/supported#boxcar). + +## Example Usage + +```terraform +resource "radarr_notification_boxcar" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + token = "Token" +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationBoxcar name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `token` (String, Sensitive) Token. + +### Optional + +- `tags` (Set of Number) List of associated tags. + +### 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_boxcar.example 1 +``` diff --git a/docs/resources/notification_discord.md b/docs/resources/notification_discord.md new file mode 100644 index 00000000..26c3e3d0 --- /dev/null +++ b/docs/resources/notification_discord.md @@ -0,0 +1,79 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_discord Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Discord resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Discord https://wiki.servarr.com/radarr/supported#discord. +--- + +# radarr_notification_discord (Resource) + +Notification Discord resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Discord](https://wiki.servarr.com/radarr/supported#discord). + +## Example Usage + +```terraform +resource "radarr_notification_discord" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + web_hook_url = "http://discord-web-hook.com" + username = "User" + avatar = "https://i.imgur.com/oBPXx0D.png" + grab_fields = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + import_fields = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationDiscord name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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. +- `web_hook_url` (String) Web hook URL. + +### Optional + +- `author` (String) Author. +- `avatar` (String) Avatar. +- `grab_fields` (Set of Number) Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart. +- `import_fields` (Set of Number) Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart. +- `tags` (Set of Number) List of associated tags. +- `username` (String) Username. + +### 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_discord.example 1 +``` diff --git a/docs/resources/notification_email.md b/docs/resources/notification_email.md new file mode 100644 index 00000000..473e4a3c --- /dev/null +++ b/docs/resources/notification_email.md @@ -0,0 +1,80 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_email Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Email resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Email https://wiki.servarr.com/radarr/supported#email. +--- + +# radarr_notification_email (Resource) + +Notification Email resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Email](https://wiki.servarr.com/radarr/supported#email). + +## Example Usage + +```terraform +resource "radarr_notification_email" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + server = "http://email-server.net" + port = 587 + from = "from_email@example.com" + to = ["user1@example.com", "user2@example.com"] +} +``` + + +## Schema + +### Required + +- `from` (String) From. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationEmail name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `server` (String) Server. +- `to` (Set of String) To. + +### Optional + +- `bcc` (Set of String) Bcc. +- `cc` (Set of String) Cc. +- `password` (String, Sensitive) Password. +- `port` (Number) Port. +- `require_encryption` (Boolean) Require encryption flag. +- `tags` (Set of Number) List of associated tags. +- `username` (String) Username. + +### 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_email.example 1 +``` diff --git a/docs/resources/notification_emby.md b/docs/resources/notification_emby.md new file mode 100644 index 00000000..681416a3 --- /dev/null +++ b/docs/resources/notification_emby.md @@ -0,0 +1,78 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_emby Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Emby resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Emby https://wiki.servarr.com/radarr/supported#mediabrowser. +--- + +# radarr_notification_emby (Resource) + +Notification Emby resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Emby](https://wiki.servarr.com/radarr/supported#mediabrowser). + +## Example Usage + +```terraform +resource "radarr_notification_emby" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + host = "emby.lcl" + port = 8096 + api_key = "API_Key" +} +``` + + +## Schema + +### Required + +- `api_key` (String, Sensitive) API key. +- `host` (String) Host. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationEmby name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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 + +- `notify` (Boolean) Notify flag. +- `port` (Number) Port. +- `tags` (Set of Number) List of associated tags. +- `update_library` (Boolean) Update library flag. +- `use_ssl` (Boolean) Use SSL 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_emby.example 1 +``` diff --git a/docs/resources/notification_gotify.md b/docs/resources/notification_gotify.md new file mode 100644 index 00000000..05ebe5cc --- /dev/null +++ b/docs/resources/notification_gotify.md @@ -0,0 +1,73 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_gotify Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Gotify resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Gotify https://wiki.servarr.com/radarr/supported#gotify. +--- + +# radarr_notification_gotify (Resource) + +Notification Gotify resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Gotify](https://wiki.servarr.com/radarr/supported#gotify). + +## Example Usage + +```terraform +resource "radarr_notification_gotify" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + server = "http://gotify-server.net" + app_token = "Token" + priority = 5 +} +``` + + +## Schema + +### Required + +- `app_token` (String, Sensitive) App token. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationGotify name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `server` (String) Server. + +### Optional + +- `priority` (Number) Priority. `0` Min, `2` Low, `5` Normal, `8` High. +- `tags` (Set of Number) List of associated tags. + +### 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_gotify.example 1 +``` diff --git a/docs/resources/notification_join.md b/docs/resources/notification_join.md new file mode 100644 index 00000000..029ea7ca --- /dev/null +++ b/docs/resources/notification_join.md @@ -0,0 +1,73 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_join Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Join resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Join https://wiki.servarr.com/radarr/supported#join. +--- + +# radarr_notification_join (Resource) + +Notification Join resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Join](https://wiki.servarr.com/radarr/supported#join). + +## Example Usage + +```terraform +resource "radarr_notification_join" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + device_names = "device1,device2" + api_key = "Key" + priority = 2 +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationJoin name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `api_key` (String, Sensitive) API key. +- `device_names` (String) Device names. Comma separated list. +- `priority` (Number) Priority. `-2` Silent, `-1` Quiet, `0` Normal, `1` High, `2` Emergency. +- `tags` (Set of Number) List of associated tags. + +### 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_join.example 1 +``` diff --git a/docs/resources/notification_kodi.md b/docs/resources/notification_kodi.md new file mode 100644 index 00000000..ef3cf0f7 --- /dev/null +++ b/docs/resources/notification_kodi.md @@ -0,0 +1,84 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_kodi Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Kodi resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Kodi https://wiki.servarr.com/radarr/supported#xbmc. +--- + +# radarr_notification_kodi (Resource) + +Notification Kodi resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Kodi](https://wiki.servarr.com/radarr/supported#xbmc). + +## Example Usage + +```terraform +resource "radarr_notification_kodi" "example" { + on_grab = false + 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 = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + host = "http://kodi.com" + port = 8080 + username = "User" + password = "MyPass" + notify = true +} +``` + + +## Schema + +### Required + +- `host` (String) Host. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationKodi name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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. +- `port` (Number) Port. + +### Optional + +- `always_update` (Boolean) Always update flag. +- `clean_library` (Boolean) Clean library flag. +- `display_time` (Number) Display time. +- `notify` (Boolean) Notification flag. +- `password` (String, Sensitive) Password. +- `tags` (Set of Number) List of associated tags. +- `update_library` (Boolean) Update library flag. +- `use_ssl` (Boolean) Use SSL flag. +- `username` (String) Username. + +### 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_kodi.example 1 +``` diff --git a/docs/resources/notification_mailgun.md b/docs/resources/notification_mailgun.md new file mode 100644 index 00000000..262b843a --- /dev/null +++ b/docs/resources/notification_mailgun.md @@ -0,0 +1,75 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_mailgun Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Mailgun resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Mailgun https://wiki.servarr.com/radarr/supported#mailgun. +--- + +# radarr_notification_mailgun (Resource) + +Notification Mailgun resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Mailgun](https://wiki.servarr.com/radarr/supported#mailgun). + +## Example Usage + +```terraform +resource "radarr_notification_mailgun" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIkey" + from = "from_mailgun@example.com" + recipients = ["user1@example.com", "user2@example.com"] +} +``` + + +## Schema + +### Required + +- `from` (String) From. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationMailgun name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `recipients` (Set of String) Recipients. + +### Optional + +- `api_key` (String, Sensitive) API key. +- `sender_domain` (String) Sender domain. +- `tags` (Set of Number) List of associated tags. +- `use_eu_endpoint` (Boolean) Use EU endpoint 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_mailgun.example 1 +``` diff --git a/docs/resources/notification_notifiarr.md b/docs/resources/notification_notifiarr.md new file mode 100644 index 00000000..f2aa5880 --- /dev/null +++ b/docs/resources/notification_notifiarr.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_notifiarr Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Notifiarr resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Notifiarr https://wiki.servarr.com/radarr/supported#notifiarr. +--- + +# radarr_notification_notifiarr (Resource) + +Notification Notifiarr resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Notifiarr](https://wiki.servarr.com/radarr/supported#notifiarr). + +## Example Usage + +```terraform +resource "radarr_notification_notifiarr" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Token" + instance_name = "radarr" +} +``` + + +## Schema + +### Required + +- `api_key` (String, Sensitive) API key. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationNotifiarr name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `instance_name` (String) Instance Name. +- `tags` (Set of Number) List of associated tags. + +### 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_notifiarr.example 1 +``` diff --git a/docs/resources/notification_ntfy.md b/docs/resources/notification_ntfy.md new file mode 100644 index 00000000..46fce6eb --- /dev/null +++ b/docs/resources/notification_ntfy.md @@ -0,0 +1,80 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_ntfy Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Ntfy resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Ntfy https://wiki.servarr.com/radarr/supported#ntfy. +--- + +# radarr_notification_ntfy (Resource) + +Notification Ntfy resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Ntfy](https://wiki.servarr.com/radarr/supported#ntfy). + +## Example Usage + +```terraform +resource "radarr_notification_ntfy" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + priority = 1 + server_url = "https://ntfy.sh" + username = "User" + password = "Pass" + topics = ["Topic1234", "Topic4321"] + field_tags = ["warning", "skull"] +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationNtfy name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `topics` (Set of String) Topics. + +### Optional + +- `click_url` (String) Click URL. +- `field_tags` (Set of String) Tags and emojis. +- `password` (String, Sensitive) Password. +- `priority` (Number) Priority. `1` Min, `2` Low, `3` Default, `4` High, `5` Max. +- `server_url` (String) Server URL. +- `tags` (Set of Number) List of associated tags. +- `username` (String) Username. + +### 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_ntfy.example 1 +``` diff --git a/docs/resources/notification_plex.md b/docs/resources/notification_plex.md new file mode 100644 index 00000000..20aa3820 --- /dev/null +++ b/docs/resources/notification_plex.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_plex Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Plex resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Plex https://wiki.servarr.com/radarr/supported#plexserver. +--- + +# radarr_notification_plex (Resource) + +Notification Plex resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Plex](https://wiki.servarr.com/radarr/supported#plexserver). + +## Example Usage + +```terraform +resource "radarr_notification_plex" "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" + + host = "plex.lcl" + port = 32400 + auth_token = "AuthTOKEN" +} +``` + + +## Schema + +### Required + +- `auth_token` (String, Sensitive) Auth Token. +- `host` (String) Host. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationPlex 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 + +- `port` (Number) Port. +- `tags` (Set of Number) List of associated tags. +- `update_library` (Boolean) Update library flag. +- `use_ssl` (Boolean) Use SSL 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_plex.example 1 +``` diff --git a/docs/resources/notification_prowl.md b/docs/resources/notification_prowl.md new file mode 100644 index 00000000..eec51324 --- /dev/null +++ b/docs/resources/notification_prowl.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_prowl Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Prowl resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Prowl https://wiki.servarr.com/radarr/supported#prowl. +--- + +# radarr_notification_prowl (Resource) + +Notification Prowl resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Prowl](https://wiki.servarr.com/radarr/supported#prowl). + +## Example Usage + +```terraform +resource "radarr_notification_prowl" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIKey" + priority = -2 +} +``` + + +## Schema + +### Required + +- `api_key` (String, Sensitive) API key. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationProwl name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `priority` (Number) Priority.`-2` Very Low, `-1` Low, `0` Normal, `1` High, `2` Emergency. +- `tags` (Set of Number) List of associated tags. + +### 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_prowl.example 1 +``` diff --git a/docs/resources/notification_pushbullet.md b/docs/resources/notification_pushbullet.md new file mode 100644 index 00000000..5f22a123 --- /dev/null +++ b/docs/resources/notification_pushbullet.md @@ -0,0 +1,73 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_pushbullet Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Pushbullet resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Pushbullet https://wiki.servarr.com/radarr/supported#pushbullet. +--- + +# radarr_notification_pushbullet (Resource) + +Notification Pushbullet resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Pushbullet](https://wiki.servarr.com/radarr/supported#pushbullet). + +## Example Usage + +```terraform +resource "radarr_notification_pushbullet" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Token" + device_ids = ["test"] +} +``` + + +## Schema + +### Required + +- `api_key` (String, Sensitive) API key. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationPushbullet name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `channel_tags` (Set of String) List of channel tags. +- `device_ids` (Set of String) List of devices IDs. +- `sender_id` (String) Sender ID. +- `tags` (Set of Number) List of associated tags. + +### 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_pushbullet.example 1 +``` diff --git a/docs/resources/notification_pushover.md b/docs/resources/notification_pushover.md new file mode 100644 index 00000000..64e54d06 --- /dev/null +++ b/docs/resources/notification_pushover.md @@ -0,0 +1,76 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_pushover Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Pushover resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Pushover https://wiki.servarr.com/radarr/supported#pushover. +--- + +# radarr_notification_pushover (Resource) + +Notification Pushover resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Pushover](https://wiki.servarr.com/radarr/supported#pushover). + +## Example Usage + +```terraform +resource "radarr_notification_join" "example" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Key" + priority = 2 +} +``` + + +## Schema + +### Required + +- `api_key` (String, Sensitive) API key. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationPushover name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `devices` (Set of String) List of devices. +- `expire` (Number) Expire. +- `priority` (Number) Priority. `-2` Silent, `-1` Quiet, `0` Normal, `1` High, `2` Emergency, `8` High. +- `retry` (Number) Retry. +- `sound` (String) Sound. +- `tags` (Set of Number) List of associated tags. +- `user_key` (String, Sensitive) User key. + +### 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_pushover.example 1 +``` diff --git a/docs/resources/notification_sendgrid.md b/docs/resources/notification_sendgrid.md new file mode 100644 index 00000000..9290e986 --- /dev/null +++ b/docs/resources/notification_sendgrid.md @@ -0,0 +1,73 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_sendgrid Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Sendgrid resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Sendgrid https://wiki.servarr.com/radarr/supported#sendgrid. +--- + +# radarr_notification_sendgrid (Resource) + +Notification Sendgrid resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Sendgrid](https://wiki.servarr.com/radarr/supported#sendgrid). + +## Example Usage + +```terraform +resource "radarr_notification_sendgrid" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIkey" + from = "from_sendgrid@example.com" + recipients = ["user1@example.com", "user2@example.com"] +} +``` + + +## Schema + +### Required + +- `from` (String) From. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationSendgrid name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. +- `recipients` (Set of String) Recipients. + +### Optional + +- `api_key` (String, Sensitive) API key. +- `tags` (Set of Number) List of associated tags. + +### 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_sendgrid.example 1 +``` diff --git a/docs/resources/notification_simplepush.md b/docs/resources/notification_simplepush.md new file mode 100644 index 00000000..b4f43fc1 --- /dev/null +++ b/docs/resources/notification_simplepush.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_simplepush Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Simplepush resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Simplepush https://wiki.servarr.com/radarr/supported#simplepush. +--- + +# radarr_notification_simplepush (Resource) + +Notification Simplepush resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Simplepush](https://wiki.servarr.com/radarr/supported#simplepush). + +## Example Usage + +```terraform +resource "radarr_notification_simplepush" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + key = "Token" + event = "ringtone:default" +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `key` (String, Sensitive) Key. +- `name` (String) NotificationSimplepush name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `event` (String) Event. +- `tags` (Set of Number) List of associated tags. + +### 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_simplepush.example 1 +``` diff --git a/docs/resources/notification_slack.md b/docs/resources/notification_slack.md new file mode 100644 index 00000000..ddf9e99d --- /dev/null +++ b/docs/resources/notification_slack.md @@ -0,0 +1,76 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_slack Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Slack resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Slack https://wiki.servarr.com/radarr/supported#slack. +--- + +# radarr_notification_slack (Resource) + +Notification Slack resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Slack](https://wiki.servarr.com/radarr/supported#slack). + +## Example Usage + +```terraform +resource "radarr_notification_slack" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + web_hook_url = "http://my.slack.com/test" + username = "user" + channel = "example-channel" +} +``` + + +## Schema + +### Required + +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationSlack name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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. +- `username` (String) Username. +- `web_hook_url` (String) URL. + +### Optional + +- `channel` (String) Channel. +- `icon` (String) Icon. +- `tags` (Set of Number) List of associated tags. + +### 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_slack.example 1 +``` 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/docs/resources/notification_telegram.md b/docs/resources/notification_telegram.md new file mode 100644 index 00000000..471cf949 --- /dev/null +++ b/docs/resources/notification_telegram.md @@ -0,0 +1,72 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_telegram Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Telegram resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Telegram https://wiki.servarr.com/radarr/supported#telegram. +--- + +# radarr_notification_telegram (Resource) + +Notification Telegram resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Telegram](https://wiki.servarr.com/radarr/supported#telegram). + +## Example Usage + +```terraform +resource "radarr_notification_telegram" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + bot_token = "Token" + chat_id = "ChatID01" +} +``` + + +## Schema + +### Required + +- `bot_token` (String, Sensitive) Bot token. +- `chat_id` (String) Chat ID. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationTelegram name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `send_silently` (Boolean) Send silently flag. +- `tags` (Set of Number) List of associated tags. + +### 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_telegram.example 1 +``` diff --git a/docs/resources/notification_trakt.md b/docs/resources/notification_trakt.md new file mode 100644 index 00000000..bd8f7c4a --- /dev/null +++ b/docs/resources/notification_trakt.md @@ -0,0 +1,67 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_trakt Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Trakt resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Trakt https://wiki.servarr.com/radarr/supported#trakt. +--- + +# radarr_notification_trakt (Resource) + +Notification Trakt resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Trakt](https://wiki.servarr.com/radarr/supported#trakt). + +## Example Usage + +```terraform +resource "radarr_notification_trakt" "example" { + on_download = true + on_upgrade = true + 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" + + auth_user = "User" + access_token = "AuthTOKEN" +} +``` + + +## Schema + +### Required + +- `access_token` (String, Sensitive) Access Token. +- `auth_user` (String) Auth user. +- `include_health_warnings` (Boolean) Include health warnings. +- `name` (String) NotificationTrakt 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `refresh_token` (String, Sensitive) Access Token. +- `tags` (Set of Number) List of associated tags. + +### Read-Only + +- `expires` (String) expires. +- `id` (Number) Notification ID. + +## Import + +Import is supported using the following syntax: + +```shell +# import using the API/UI ID +terraform import radarr_notification_trakt.example 1 +``` diff --git a/docs/resources/notification_twitter.md b/docs/resources/notification_twitter.md new file mode 100644 index 00000000..d130cb3f --- /dev/null +++ b/docs/resources/notification_twitter.md @@ -0,0 +1,78 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_notification_twitter Resource - terraform-provider-radarr" +subcategory: "Notifications" +description: |- + Notification Twitter resource. + For more information refer to Notification https://wiki.servarr.com/radarr/settings#connect and Twitter https://wiki.servarr.com/radarr/supported#twitter. +--- + +# radarr_notification_twitter (Resource) + +Notification Twitter resource. +For more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Twitter](https://wiki.servarr.com/radarr/supported#twitter). + +## Example Usage + +```terraform +resource "radarr_notification_twitter" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + access_token = "Token" + access_token_secret = "TokenSecret" + consumer_key = "Key" + consumer_secret = "Secret" + mention = "someone" +} +``` + + +## Schema + +### Required + +- `access_token` (String, Sensitive) Access token. +- `access_token_secret` (String, Sensitive) Access token secret. +- `consumer_key` (String, Sensitive) Consumer Key. +- `consumer_secret` (String, Sensitive) Consumer Secret. +- `include_health_warnings` (Boolean) Include health warnings. +- `mention` (String) Mention. +- `name` (String) NotificationTwitter name. +- `on_application_update` (Boolean) On application update flag. +- `on_download` (Boolean) On download flag. +- `on_grab` (Boolean) On grab flag. +- `on_health_issue` (Boolean) On health issue 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_upgrade` (Boolean) On upgrade flag. + +### Optional + +- `direct_message` (Boolean) Direct message flag. +- `tags` (Set of Number) List of associated tags. + +### 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_twitter.example 1 +``` diff --git a/examples/resources/radarr_notification_boxcar/import.sh b/examples/resources/radarr_notification_boxcar/import.sh new file mode 100644 index 00000000..910c61b3 --- /dev/null +++ b/examples/resources/radarr_notification_boxcar/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_boxcar.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_boxcar/resource.tf b/examples/resources/radarr_notification_boxcar/resource.tf new file mode 100644 index 00000000..cf025a93 --- /dev/null +++ b/examples/resources/radarr_notification_boxcar/resource.tf @@ -0,0 +1,16 @@ +resource "radarr_notification_boxcar" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + token = "Token" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_discord/import.sh b/examples/resources/radarr_notification_discord/import.sh new file mode 100644 index 00000000..c718027e --- /dev/null +++ b/examples/resources/radarr_notification_discord/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_discord.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_discord/resource.tf b/examples/resources/radarr_notification_discord/resource.tf new file mode 100644 index 00000000..14215986 --- /dev/null +++ b/examples/resources/radarr_notification_discord/resource.tf @@ -0,0 +1,21 @@ +resource "radarr_notification_discord" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + web_hook_url = "http://discord-web-hook.com" + username = "User" + avatar = "https://i.imgur.com/oBPXx0D.png" + grab_fields = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + import_fields = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_email/import.sh b/examples/resources/radarr_notification_email/import.sh new file mode 100644 index 00000000..2488f481 --- /dev/null +++ b/examples/resources/radarr_notification_email/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_email.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_email/resource.tf b/examples/resources/radarr_notification_email/resource.tf new file mode 100644 index 00000000..023027ac --- /dev/null +++ b/examples/resources/radarr_notification_email/resource.tf @@ -0,0 +1,19 @@ +resource "radarr_notification_email" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + server = "http://email-server.net" + port = 587 + from = "from_email@example.com" + to = ["user1@example.com", "user2@example.com"] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_emby/import.sh b/examples/resources/radarr_notification_emby/import.sh new file mode 100644 index 00000000..112f9594 --- /dev/null +++ b/examples/resources/radarr_notification_emby/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_emby.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_emby/resource.tf b/examples/resources/radarr_notification_emby/resource.tf new file mode 100644 index 00000000..86a2b075 --- /dev/null +++ b/examples/resources/radarr_notification_emby/resource.tf @@ -0,0 +1,19 @@ +resource "radarr_notification_emby" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + host = "emby.lcl" + port = 8096 + api_key = "API_Key" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_gotify/import.sh b/examples/resources/radarr_notification_gotify/import.sh new file mode 100644 index 00000000..6343812c --- /dev/null +++ b/examples/resources/radarr_notification_gotify/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_gotify.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_gotify/resource.tf b/examples/resources/radarr_notification_gotify/resource.tf new file mode 100644 index 00000000..6076e29b --- /dev/null +++ b/examples/resources/radarr_notification_gotify/resource.tf @@ -0,0 +1,18 @@ +resource "radarr_notification_gotify" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + server = "http://gotify-server.net" + app_token = "Token" + priority = 5 +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_join/import.sh b/examples/resources/radarr_notification_join/import.sh new file mode 100644 index 00000000..be4a70f9 --- /dev/null +++ b/examples/resources/radarr_notification_join/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_join.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_join/resource.tf b/examples/resources/radarr_notification_join/resource.tf new file mode 100644 index 00000000..761505e7 --- /dev/null +++ b/examples/resources/radarr_notification_join/resource.tf @@ -0,0 +1,18 @@ +resource "radarr_notification_join" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + device_names = "device1,device2" + api_key = "Key" + priority = 2 +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_kodi/import.sh b/examples/resources/radarr_notification_kodi/import.sh new file mode 100644 index 00000000..7a387033 --- /dev/null +++ b/examples/resources/radarr_notification_kodi/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_kodi.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_kodi/resource.tf b/examples/resources/radarr_notification_kodi/resource.tf new file mode 100644 index 00000000..73434b8b --- /dev/null +++ b/examples/resources/radarr_notification_kodi/resource.tf @@ -0,0 +1,21 @@ +resource "radarr_notification_kodi" "example" { + on_grab = false + 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 = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + host = "http://kodi.com" + port = 8080 + username = "User" + password = "MyPass" + notify = true +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_mailgun/import.sh b/examples/resources/radarr_notification_mailgun/import.sh new file mode 100644 index 00000000..9d2f6b58 --- /dev/null +++ b/examples/resources/radarr_notification_mailgun/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_mailgun.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_mailgun/resource.tf b/examples/resources/radarr_notification_mailgun/resource.tf new file mode 100644 index 00000000..4bbe9439 --- /dev/null +++ b/examples/resources/radarr_notification_mailgun/resource.tf @@ -0,0 +1,18 @@ +resource "radarr_notification_mailgun" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIkey" + from = "from_mailgun@example.com" + recipients = ["user1@example.com", "user2@example.com"] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_notifiarr/import.sh b/examples/resources/radarr_notification_notifiarr/import.sh new file mode 100644 index 00000000..b3ccf855 --- /dev/null +++ b/examples/resources/radarr_notification_notifiarr/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_notifiarr.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_notifiarr/resource.tf b/examples/resources/radarr_notification_notifiarr/resource.tf new file mode 100644 index 00000000..c62fa115 --- /dev/null +++ b/examples/resources/radarr_notification_notifiarr/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_notifiarr" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Token" + instance_name = "radarr" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_ntfy/import.sh b/examples/resources/radarr_notification_ntfy/import.sh new file mode 100644 index 00000000..4aa93c3b --- /dev/null +++ b/examples/resources/radarr_notification_ntfy/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_ntfy.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_ntfy/resource.tf b/examples/resources/radarr_notification_ntfy/resource.tf new file mode 100644 index 00000000..1a8b2869 --- /dev/null +++ b/examples/resources/radarr_notification_ntfy/resource.tf @@ -0,0 +1,21 @@ +resource "radarr_notification_ntfy" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + priority = 1 + server_url = "https://ntfy.sh" + username = "User" + password = "Pass" + topics = ["Topic1234", "Topic4321"] + field_tags = ["warning", "skull"] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_plex/import.sh b/examples/resources/radarr_notification_plex/import.sh new file mode 100644 index 00000000..9ffa0ac1 --- /dev/null +++ b/examples/resources/radarr_notification_plex/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_plex.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_plex/resource.tf b/examples/resources/radarr_notification_plex/resource.tf new file mode 100644 index 00000000..d7065a19 --- /dev/null +++ b/examples/resources/radarr_notification_plex/resource.tf @@ -0,0 +1,16 @@ +resource "radarr_notification_plex" "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" + + host = "plex.lcl" + port = 32400 + auth_token = "AuthTOKEN" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_prowl/import.sh b/examples/resources/radarr_notification_prowl/import.sh new file mode 100644 index 00000000..90978e00 --- /dev/null +++ b/examples/resources/radarr_notification_prowl/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_prowl.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_prowl/resource.tf b/examples/resources/radarr_notification_prowl/resource.tf new file mode 100644 index 00000000..458e319d --- /dev/null +++ b/examples/resources/radarr_notification_prowl/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_prowl" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIKey" + priority = -2 +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_pushbullet/import.sh b/examples/resources/radarr_notification_pushbullet/import.sh new file mode 100644 index 00000000..a77fb732 --- /dev/null +++ b/examples/resources/radarr_notification_pushbullet/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_pushbullet.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_pushbullet/resource.tf b/examples/resources/radarr_notification_pushbullet/resource.tf new file mode 100644 index 00000000..1405533e --- /dev/null +++ b/examples/resources/radarr_notification_pushbullet/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_pushbullet" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Token" + device_ids = ["test"] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_pushover/import.sh b/examples/resources/radarr_notification_pushover/import.sh new file mode 100644 index 00000000..de60a234 --- /dev/null +++ b/examples/resources/radarr_notification_pushover/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_pushover.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_pushover/resource.tf b/examples/resources/radarr_notification_pushover/resource.tf new file mode 100644 index 00000000..e6e4f5e1 --- /dev/null +++ b/examples/resources/radarr_notification_pushover/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_join" "example" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "Key" + priority = 2 +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_sendgrid/import.sh b/examples/resources/radarr_notification_sendgrid/import.sh new file mode 100644 index 00000000..f241a529 --- /dev/null +++ b/examples/resources/radarr_notification_sendgrid/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_sendgrid.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_sendgrid/resource.tf b/examples/resources/radarr_notification_sendgrid/resource.tf new file mode 100644 index 00000000..19297cd6 --- /dev/null +++ b/examples/resources/radarr_notification_sendgrid/resource.tf @@ -0,0 +1,18 @@ +resource "radarr_notification_sendgrid" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + api_key = "APIkey" + from = "from_sendgrid@example.com" + recipients = ["user1@example.com", "user2@example.com"] +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_simplepush/import.sh b/examples/resources/radarr_notification_simplepush/import.sh new file mode 100644 index 00000000..db5b1214 --- /dev/null +++ b/examples/resources/radarr_notification_simplepush/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_simplepush.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_simplepush/resource.tf b/examples/resources/radarr_notification_simplepush/resource.tf new file mode 100644 index 00000000..dd0b7c7d --- /dev/null +++ b/examples/resources/radarr_notification_simplepush/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_simplepush" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + key = "Token" + event = "ringtone:default" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_slack/import.sh b/examples/resources/radarr_notification_slack/import.sh new file mode 100644 index 00000000..f9950f2a --- /dev/null +++ b/examples/resources/radarr_notification_slack/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_slack.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_slack/resource.tf b/examples/resources/radarr_notification_slack/resource.tf new file mode 100644 index 00000000..a902fbb4 --- /dev/null +++ b/examples/resources/radarr_notification_slack/resource.tf @@ -0,0 +1,19 @@ +resource "radarr_notification_slack" "example" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + web_hook_url = "http://my.slack.com/test" + username = "user" + channel = "example-channel" +} \ No newline at end of file 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/examples/resources/radarr_notification_telegram/import.sh b/examples/resources/radarr_notification_telegram/import.sh new file mode 100644 index 00000000..d376737a --- /dev/null +++ b/examples/resources/radarr_notification_telegram/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_telegram.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_telegram/resource.tf b/examples/resources/radarr_notification_telegram/resource.tf new file mode 100644 index 00000000..9b90a85a --- /dev/null +++ b/examples/resources/radarr_notification_telegram/resource.tf @@ -0,0 +1,17 @@ +resource "radarr_notification_telegram" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + bot_token = "Token" + chat_id = "ChatID01" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_trakt/import.sh b/examples/resources/radarr_notification_trakt/import.sh new file mode 100644 index 00000000..64529aee --- /dev/null +++ b/examples/resources/radarr_notification_trakt/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_trakt.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_trakt/resource.tf b/examples/resources/radarr_notification_trakt/resource.tf new file mode 100644 index 00000000..4dd95235 --- /dev/null +++ b/examples/resources/radarr_notification_trakt/resource.tf @@ -0,0 +1,14 @@ +resource "radarr_notification_trakt" "example" { + on_download = true + on_upgrade = true + 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" + + auth_user = "User" + access_token = "AuthTOKEN" +} \ No newline at end of file diff --git a/examples/resources/radarr_notification_twitter/import.sh b/examples/resources/radarr_notification_twitter/import.sh new file mode 100644 index 00000000..b7157fe8 --- /dev/null +++ b/examples/resources/radarr_notification_twitter/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_notification_twitter.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_notification_twitter/resource.tf b/examples/resources/radarr_notification_twitter/resource.tf new file mode 100644 index 00000000..e0d8440c --- /dev/null +++ b/examples/resources/radarr_notification_twitter/resource.tf @@ -0,0 +1,20 @@ +resource "radarr_notification_twitter" "example" { + on_grab = false + on_download = true + on_upgrade = true + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = true + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "Example" + + access_token = "Token" + access_token_secret = "TokenSecret" + consumer_key = "Key" + consumer_secret = "Secret" + mention = "someone" +} \ No newline at end of file diff --git a/internal/provider/notification_boxcar_resource.go b/internal/provider/notification_boxcar_resource.go new file mode 100644 index 00000000..657b22fc --- /dev/null +++ b/internal/provider/notification_boxcar_resource.go @@ -0,0 +1,343 @@ +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 ( + notificationBoxcarResourceName = "notification_boxcar" + NotificationBoxcarImplementation = "Boxcar" + NotificationBoxcarConfigContrat = "BoxcarSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationBoxcarResource{} +var _ resource.ResourceWithImportState = &NotificationBoxcarResource{} + +func NewNotificationBoxcarResource() resource.Resource { + return &NotificationBoxcarResource{} +} + +// NotificationBoxcarResource defines the notification implementation. +type NotificationBoxcarResource struct { + client *radarr.Radarr +} + +// NotificationBoxcar describes the notification data model. +type NotificationBoxcar struct { + Tags types.Set `tfsdk:"tags"` + Token types.String `tfsdk:"token"` + Name types.String `tfsdk:"name"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationBoxcar) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Token: n.Token, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationBoxcar) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Token = notification.Token + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationBoxcarResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationBoxcarResourceName +} + +func (r *NotificationBoxcarResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Boxcar resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Boxcar](https://wiki.servarr.com/radarr/supported#boxcar).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationBoxcar 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 + "token": schema.StringAttribute{ + MarkdownDescription: "Token.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationBoxcarResource) 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 *NotificationBoxcarResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationBoxcar + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationBoxcar + 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", notificationBoxcarResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationBoxcarResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationBoxcarResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationBoxcar + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationBoxcar 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", notificationBoxcarResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationBoxcarResourceName+": "+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 *NotificationBoxcarResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationBoxcar + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationBoxcar + 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", notificationBoxcarResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationBoxcarResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationBoxcarResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationBoxcar + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationBoxcar 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", notificationBoxcarResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationBoxcarResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationBoxcarResource) 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 "+notificationBoxcarResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationBoxcar) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationBoxcar) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationBoxcarConfigContrat, + Implementation: NotificationBoxcarImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_boxcar_resource_test.go b/internal/provider/notification_boxcar_resource_test.go new file mode 100644 index 00000000..7e0c6752 --- /dev/null +++ b/internal/provider/notification_boxcar_resource_test.go @@ -0,0 +1,61 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationBoxcarResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationBoxcarResourceConfig("resourceBoxcarTest", "token123"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_boxcar.test", "token", "token123"), + resource.TestCheckResourceAttrSet("radarr_notification_boxcar.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationBoxcarResourceConfig("resourceBoxcarTest", "token234"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_boxcar.test", "token", "token234"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_boxcar.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationBoxcarResourceConfig(name, token string) string { + return fmt.Sprintf(` + resource "radarr_notification_boxcar" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + token = "%s" + }`, name, token) +} diff --git a/internal/provider/notification_data_source.go b/internal/provider/notification_data_source.go index 493668f3..e92329ec 100644 --- a/internal/provider/notification_data_source.go +++ b/internal/provider/notification_data_source.go @@ -137,16 +137,13 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche MarkdownDescription: "Use SSL flag.", Computed: true, }, - "port": schema.Int64Attribute{ - MarkdownDescription: "Port.", - Computed: true, - }, - "grab_fields": schema.Int64Attribute{ - MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore.", + + "display_time": schema.Int64Attribute{ + MarkdownDescription: "Display time.", Computed: true, }, - "import_fields": schema.Int64Attribute{ - MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", Computed: true, }, "method": schema.Int64Attribute{ @@ -205,18 +202,10 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche MarkdownDescription: "Instance name.", Computed: true, }, - "bcc": schema.StringAttribute{ - MarkdownDescription: "Bcc.", - Computed: true, - }, "bot_token": schema.StringAttribute{ MarkdownDescription: "Bot token.", Computed: true, }, - "cc": schema.StringAttribute{ - MarkdownDescription: "Cc.", - Computed: true, - }, "channel": schema.StringAttribute{ MarkdownDescription: "Channel.", Computed: true, @@ -237,10 +226,6 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche MarkdownDescription: "Device names.", Computed: true, }, - "display_time": schema.StringAttribute{ - MarkdownDescription: "Display time.", - Computed: true, - }, "expires": schema.StringAttribute{ MarkdownDescription: "Expires.", Computed: true, @@ -293,10 +278,6 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche MarkdownDescription: "Sound.", Computed: true, }, - "to": schema.StringAttribute{ - MarkdownDescription: "To.", - Computed: true, - }, "token": schema.StringAttribute{ MarkdownDescription: "Token.", Computed: true, @@ -344,7 +325,7 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche "device_ids": schema.SetAttribute{ MarkdownDescription: "Device IDs.", Computed: true, - ElementType: types.Int64Type, + ElementType: types.StringType, }, "channel_tags": schema.SetAttribute{ MarkdownDescription: "Channel tags.", @@ -357,12 +338,22 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche ElementType: types.StringType, }, "topics": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Topics.", Computed: true, ElementType: types.StringType, }, + "grab_fields": schema.SetAttribute{ + MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore.", + Computed: true, + ElementType: types.Int64Type, + }, + "import_fields": schema.SetAttribute{ + MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + Computed: true, + ElementType: types.Int64Type, + }, "field_tags": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Specific tags.", Computed: true, ElementType: types.StringType, }, @@ -371,6 +362,21 @@ func (d *NotificationDataSource) Schema(ctx context.Context, req datasource.Sche Computed: true, ElementType: types.StringType, }, + "to": schema.SetAttribute{ + MarkdownDescription: "To.", + Computed: true, + ElementType: types.StringType, + }, + "cc": schema.SetAttribute{ + MarkdownDescription: "Cc.", + Computed: true, + ElementType: types.StringType, + }, + "bcc": schema.SetAttribute{ + MarkdownDescription: "Bcc.", + Computed: true, + ElementType: types.StringType, + }, }, } } diff --git a/internal/provider/notification_discord_resource.go b/internal/provider/notification_discord_resource.go new file mode 100644 index 00000000..55400800 --- /dev/null +++ b/internal/provider/notification_discord_resource.go @@ -0,0 +1,384 @@ +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 ( + notificationDiscordResourceName = "notification_discord" + NotificationDiscordImplementation = "Discord" + NotificationDiscordConfigContrat = "DiscordSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationDiscordResource{} +var _ resource.ResourceWithImportState = &NotificationDiscordResource{} + +func NewNotificationDiscordResource() resource.Resource { + return &NotificationDiscordResource{} +} + +// NotificationDiscordResource defines the notification implementation. +type NotificationDiscordResource struct { + client *radarr.Radarr +} + +// NotificationDiscord describes the notification data model. +type NotificationDiscord struct { + Tags types.Set `tfsdk:"tags"` + ImportFields types.Set `tfsdk:"import_fields"` + GrabFields types.Set `tfsdk:"grab_fields"` + WebHookURL types.String `tfsdk:"web_hook_url"` + Name types.String `tfsdk:"name"` + Username types.String `tfsdk:"username"` + Avatar types.String `tfsdk:"avatar"` + Author types.String `tfsdk:"author"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + OnMovieFileDeleteForUpgrade types.Bool `tfsdk:"on_movie_file_delete_for_upgrade"` + OnMovieFileDelete types.Bool `tfsdk:"on_movie_file_delete"` + IncludeHealthWarnings types.Bool `tfsdk:"include_health_warnings"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + 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 NotificationDiscord) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + ImportFields: n.ImportFields, + GrabFields: n.GrabFields, + WebHookURL: n.WebHookURL, + Avatar: n.Avatar, + Username: n.Username, + Author: n.Author, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnRename: n.OnRename, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationDiscord) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.GrabFields = notification.GrabFields + n.ImportFields = notification.ImportFields + n.WebHookURL = notification.WebHookURL + n.Avatar = notification.Avatar + n.Username = notification.Username + n.Author = notification.Author + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieDelete = notification.OnMovieDelete + n.OnRename = notification.OnRename + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationDiscordResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationDiscordResourceName +} + +func (r *NotificationDiscordResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Discord resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Discord](https://wiki.servarr.com/radarr/supported#discord).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "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_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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationDiscord 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 + "web_hook_url": schema.StringAttribute{ + MarkdownDescription: "Web hook URL.", + Required: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Optional: true, + Computed: true, + }, + "avatar": schema.StringAttribute{ + MarkdownDescription: "Avatar.", + Optional: true, + Computed: true, + }, + "author": schema.StringAttribute{ + MarkdownDescription: "Author.", + Optional: true, + Computed: true, + }, + "grab_fields": schema.SetAttribute{ + MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + "import_fields": schema.SetAttribute{ + MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + }, + } +} + +func (r *NotificationDiscordResource) 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 *NotificationDiscordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationDiscord + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationDiscord + 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", notificationDiscordResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationDiscordResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationDiscordResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationDiscord + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationDiscord 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", notificationDiscordResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationDiscordResourceName+": "+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 *NotificationDiscordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationDiscord + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationDiscord + 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", notificationDiscordResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationDiscordResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationDiscordResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationDiscord + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationDiscord 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", notificationDiscordResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationDiscordResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationDiscordResource) 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 "+notificationDiscordResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationDiscord) 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), + 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 *NotificationDiscord) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnRename: n.OnRename.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationDiscordConfigContrat, + Implementation: NotificationDiscordImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_discord_resource_test.go b/internal/provider/notification_discord_resource_test.go new file mode 100644 index 00000000..58c092eb --- /dev/null +++ b/internal/provider/notification_discord_resource_test.go @@ -0,0 +1,65 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationDiscordResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationDiscordResourceConfig("resourceDiscordTest", "dog-picture"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_discord.test", "avatar", "dog-picture"), + resource.TestCheckResourceAttrSet("radarr_notification_discord.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationDiscordResourceConfig("resourceDiscordTest", "cat-picture"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_discord.test", "avatar", "cat-picture"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_discord.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationDiscordResourceConfig(name, avatar string) string { + return fmt.Sprintf(` + resource "radarr_notification_discord" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_rename = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + web_hook_url = "http://discord-web-hook.com" + username = "User" + avatar = "%s" + grab_fields = [0,1,2,3,4,5,6,7,8,9] + import_fields = [0,1,2,3,4,5,6,7,8,9,10,11] + }`, name, avatar) +} diff --git a/internal/provider/notification_email_resource.go b/internal/provider/notification_email_resource.go new file mode 100644 index 00000000..22029946 --- /dev/null +++ b/internal/provider/notification_email_resource.go @@ -0,0 +1,408 @@ +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 ( + notificationEmailResourceName = "notification_email" + NotificationEmailImplementation = "Email" + NotificationEmailConfigContrat = "EmailSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationEmailResource{} +var _ resource.ResourceWithImportState = &NotificationEmailResource{} + +func NewNotificationEmailResource() resource.Resource { + return &NotificationEmailResource{} +} + +// NotificationEmailResource defines the notification implementation. +type NotificationEmailResource struct { + client *radarr.Radarr +} + +// NotificationEmail describes the notification data model. +type NotificationEmail struct { + Tags types.Set `tfsdk:"tags"` + To types.Set `tfsdk:"to"` + Cc types.Set `tfsdk:"cc"` + Bcc types.Set `tfsdk:"bcc"` + From types.String `tfsdk:"from"` + Server types.String `tfsdk:"server"` + Name types.String `tfsdk:"name"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + ID types.Int64 `tfsdk:"id"` + Port types.Int64 `tfsdk:"port"` + RequireEncryption types.Bool `tfsdk:"require_encryption"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationEmail) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + From: n.From, + To: n.To, + Cc: n.Cc, + Bcc: n.Bcc, + Server: n.Server, + Port: n.Port, + Username: n.Username, + Password: n.Password, + Name: n.Name, + ID: n.ID, + RequireEncryption: n.RequireEncryption, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationEmail) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.From = notification.From + n.To = notification.To + n.Cc = notification.Cc + n.Bcc = notification.Bcc + n.Server = notification.Server + n.Port = notification.Port + n.Username = notification.Username + n.Password = notification.Password + n.Name = notification.Name + n.ID = notification.ID + n.RequireEncryption = notification.RequireEncryption + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationEmailResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationEmailResourceName +} + +func (r *NotificationEmailResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Email resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Email](https://wiki.servarr.com/radarr/supported#email).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationEmail 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 + "require_encryption": schema.BoolAttribute{ + MarkdownDescription: "Require encryption flag.", + Optional: true, + Computed: true, + }, + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", + Optional: true, + Computed: true, + }, + "server": schema.StringAttribute{ + MarkdownDescription: "Server.", + Required: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Optional: true, + Computed: true, + }, + "password": schema.StringAttribute{ + MarkdownDescription: "Password.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "from": schema.StringAttribute{ + MarkdownDescription: "From.", + Required: true, + }, + "to": schema.SetAttribute{ + MarkdownDescription: "To.", + Required: true, + ElementType: types.StringType, + }, + "cc": schema.SetAttribute{ + MarkdownDescription: "Cc.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "bcc": schema.SetAttribute{ + MarkdownDescription: "Bcc.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationEmailResource) 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 *NotificationEmailResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationEmail + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationEmail + 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", notificationEmailResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationEmailResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationEmailResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationEmail + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationEmail 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", notificationEmailResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationEmailResourceName+": "+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 *NotificationEmailResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationEmail + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationEmail + 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", notificationEmailResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationEmailResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationEmailResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationEmail + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationEmail 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", notificationEmailResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationEmailResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationEmailResource) 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 "+notificationEmailResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationEmail) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationEmail) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationEmailConfigContrat, + Implementation: NotificationEmailImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_email_resource_test.go b/internal/provider/notification_email_resource_test.go new file mode 100644 index 00000000..ca8b5e7d --- /dev/null +++ b/internal/provider/notification_email_resource_test.go @@ -0,0 +1,64 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationEmailResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationEmailResourceConfig("resourceEmailTest", "test@email.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_email.test", "from", "test@email.com"), + resource.TestCheckResourceAttrSet("radarr_notification_email.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationEmailResourceConfig("resourceEmailTest", "test123@email.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_email.test", "from", "test123@email.com"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_email.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationEmailResourceConfig(name, from string) string { + return fmt.Sprintf(` + resource "radarr_notification_email" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + server = "http://email-server.net" + port = 587 + from = "%s" + to = ["test@test.com", "test1@test.com"] + }`, name, from) +} diff --git a/internal/provider/notification_emby_resource.go b/internal/provider/notification_emby_resource.go new file mode 100644 index 00000000..6f04d79a --- /dev/null +++ b/internal/provider/notification_emby_resource.go @@ -0,0 +1,391 @@ +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 ( + notificationEmbyResourceName = "notification_emby" + NotificationEmbyImplementation = "MediaBrowser" + NotificationEmbyConfigContrat = "MediaBrowserSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationEmbyResource{} +var _ resource.ResourceWithImportState = &NotificationEmbyResource{} + +func NewNotificationEmbyResource() resource.Resource { + return &NotificationEmbyResource{} +} + +// NotificationEmbyResource defines the notification implementation. +type NotificationEmbyResource struct { + client *radarr.Radarr +} + +// NotificationEmby describes the notification data model. +type NotificationEmby struct { + Tags types.Set `tfsdk:"tags"` + Host types.String `tfsdk:"host"` + APIKey types.String `tfsdk:"api_key"` + Name types.String `tfsdk:"name"` + ID types.Int64 `tfsdk:"id"` + Port types.Int64 `tfsdk:"port"` + UpdateLibrary types.Bool `tfsdk:"update_library"` + Notify types.Bool `tfsdk:"notify"` + UseSSL types.Bool `tfsdk:"use_ssl"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + 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 NotificationEmby) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Host: n.Host, + Name: n.Name, + APIKey: n.APIKey, + ID: n.ID, + Port: n.Port, + UpdateLibrary: n.UpdateLibrary, + Notify: n.Notify, + UseSSL: n.UseSSL, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnRename: n.OnRename, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationEmby) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Host = notification.Host + n.Name = notification.Name + n.APIKey = notification.APIKey + n.ID = notification.ID + n.UpdateLibrary = notification.UpdateLibrary + n.Port = notification.Port + n.Notify = notification.Notify + n.UseSSL = notification.UseSSL + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnRename = notification.OnRename + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationEmbyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationEmbyResourceName +} + +func (r *NotificationEmbyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Emby resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Emby](https://wiki.servarr.com/radarr/supported#mediabrowser).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationEmby 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 + "use_ssl": schema.BoolAttribute{ + MarkdownDescription: "Use SSL flag.", + Optional: true, + Computed: true, + }, + "notify": schema.BoolAttribute{ + MarkdownDescription: "Notify flag.", + Optional: true, + Computed: true, + }, + "update_library": schema.BoolAttribute{ + MarkdownDescription: "Update library flag.", + Optional: true, + Computed: true, + }, + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", + Optional: true, + Computed: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Required: true, + Sensitive: true, + }, + "host": schema.StringAttribute{ + MarkdownDescription: "Host.", + Required: true, + }, + }, + } +} + +func (r *NotificationEmbyResource) 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 *NotificationEmbyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationEmby + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationEmby + 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", notificationEmbyResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationEmbyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationEmbyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationEmby + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationEmby 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", notificationEmbyResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationEmbyResourceName+": "+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 *NotificationEmbyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationEmby + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationEmby + 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", notificationEmbyResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationEmbyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationEmbyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationEmby + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationEmby 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", notificationEmbyResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationEmbyResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationEmbyResource) 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 "+notificationEmbyResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationEmby) 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 *NotificationEmby) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + 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(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationEmbyConfigContrat, + Implementation: NotificationEmbyImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_emby_resource_test.go b/internal/provider/notification_emby_resource_test.go new file mode 100644 index 00000000..ad59de17 --- /dev/null +++ b/internal/provider/notification_emby_resource_test.go @@ -0,0 +1,64 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationEmbyResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationEmbyResourceConfig("resourceEmbyTest", "token123"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_emby.test", "api_key", "token123"), + resource.TestCheckResourceAttrSet("radarr_notification_emby.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationEmbyResourceConfig("resourceEmbyTest", "token234"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_emby.test", "api_key", "token234"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_emby.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationEmbyResourceConfig(name, token string) string { + return fmt.Sprintf(` + resource "radarr_notification_emby" "test" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + host = "emby.lcl" + port = 8096 + api_key = "%s" + }`, name, token) +} diff --git a/internal/provider/notification_gotify_resource.go b/internal/provider/notification_gotify_resource.go new file mode 100644 index 00000000..cd7ae904 --- /dev/null +++ b/internal/provider/notification_gotify_resource.go @@ -0,0 +1,363 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema/validator" + "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 ( + notificationGotifyResourceName = "notification_gotify" + NotificationGotifyImplementation = "Gotify" + NotificationGotifyConfigContrat = "GotifySettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationGotifyResource{} +var _ resource.ResourceWithImportState = &NotificationGotifyResource{} + +func NewNotificationGotifyResource() resource.Resource { + return &NotificationGotifyResource{} +} + +// NotificationGotifyResource defines the notification implementation. +type NotificationGotifyResource struct { + client *radarr.Radarr +} + +// NotificationGotify describes the notification data model. +type NotificationGotify struct { + Tags types.Set `tfsdk:"tags"` + Server types.String `tfsdk:"server"` + Name types.String `tfsdk:"name"` + AppToken types.String `tfsdk:"app_token"` + Priority types.Int64 `tfsdk:"priority"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationGotify) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Server: n.Server, + AppToken: n.AppToken, + Priority: n.Priority, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationGotify) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Server = notification.Server + n.AppToken = notification.AppToken + n.Priority = notification.Priority + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationGotifyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationGotifyResourceName +} + +func (r *NotificationGotifyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Gotify resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Gotify](https://wiki.servarr.com/radarr/supported#gotify).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationGotify 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 + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority. `0` Min, `2` Low, `5` Normal, `8` High.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 2, 5, 8), + }, + }, + "server": schema.StringAttribute{ + MarkdownDescription: "Server.", + Required: true, + }, + "app_token": schema.StringAttribute{ + MarkdownDescription: "App token.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationGotifyResource) 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 *NotificationGotifyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationGotify + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationGotify + 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", notificationGotifyResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationGotifyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationGotifyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationGotify + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationGotify 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", notificationGotifyResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationGotifyResourceName+": "+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 *NotificationGotifyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationGotify + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationGotify + 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", notificationGotifyResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationGotifyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationGotifyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationGotify + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationGotify 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", notificationGotifyResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationGotifyResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationGotifyResource) 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 "+notificationGotifyResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationGotify) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationGotify) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationGotifyConfigContrat, + Implementation: NotificationGotifyImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_gotify_resource_test.go b/internal/provider/notification_gotify_resource_test.go new file mode 100644 index 00000000..9890da65 --- /dev/null +++ b/internal/provider/notification_gotify_resource_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationGotifyResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationGotifyResourceConfig("resourceGotifyTest", 0), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_gotify.test", "priority", "0"), + resource.TestCheckResourceAttrSet("radarr_notification_gotify.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationGotifyResourceConfig("resourceGotifyTest", 5), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_gotify.test", "priority", "5"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_gotify.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationGotifyResourceConfig(name string, priority int) string { + return fmt.Sprintf(` + resource "radarr_notification_gotify" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + server = "http://gotify-server.net" + app_token = "Token" + priority = %d + }`, name, priority) +} diff --git a/internal/provider/notification_join_resource.go b/internal/provider/notification_join_resource.go new file mode 100644 index 00000000..3c6efcb0 --- /dev/null +++ b/internal/provider/notification_join_resource.go @@ -0,0 +1,363 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema/validator" + "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 ( + notificationJoinResourceName = "notification_join" + NotificationJoinImplementation = "Join" + NotificationJoinConfigContrat = "JoinSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationJoinResource{} +var _ resource.ResourceWithImportState = &NotificationJoinResource{} + +func NewNotificationJoinResource() resource.Resource { + return &NotificationJoinResource{} +} + +// NotificationJoinResource defines the notification implementation. +type NotificationJoinResource struct { + client *radarr.Radarr +} + +// NotificationJoin describes the notification data model. +type NotificationJoin struct { + Tags types.Set `tfsdk:"tags"` + DeviceNames types.String `tfsdk:"device_names"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + Priority types.Int64 `tfsdk:"priority"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationJoin) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + DeviceNames: n.DeviceNames, + APIKey: n.APIKey, + Priority: n.Priority, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationJoin) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.DeviceNames = notification.DeviceNames + n.APIKey = notification.APIKey + n.Priority = notification.Priority + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationJoinResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationJoinResourceName +} + +func (r *NotificationJoinResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Join resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Join](https://wiki.servarr.com/radarr/supported#join).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationJoin 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 + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority. `-2` Silent, `-1` Quiet, `0` Normal, `1` High, `2` Emergency.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(-2, -1, 0, 1, 2), + }, + }, + "device_names": schema.StringAttribute{ + MarkdownDescription: "Device names. Comma separated list.", + Optional: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Optional: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationJoinResource) 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 *NotificationJoinResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationJoin + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationJoin + 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", notificationJoinResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationJoinResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationJoinResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationJoin + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationJoin 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", notificationJoinResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationJoinResourceName+": "+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 *NotificationJoinResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationJoin + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationJoin + 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", notificationJoinResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationJoinResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationJoinResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationJoin + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationJoin 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", notificationJoinResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationJoinResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationJoinResource) 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 "+notificationJoinResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationJoin) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationJoin) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationJoinConfigContrat, + Implementation: NotificationJoinImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_join_resource_test.go b/internal/provider/notification_join_resource_test.go new file mode 100644 index 00000000..82a9e505 --- /dev/null +++ b/internal/provider/notification_join_resource_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationJoinResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationJoinResourceConfig("resourceJoinTest", 0), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_join.test", "priority", "0"), + resource.TestCheckResourceAttrSet("radarr_notification_join.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationJoinResourceConfig("resourceJoinTest", 2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_join.test", "priority", "2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_join.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationJoinResourceConfig(name string, priority int) string { + return fmt.Sprintf(` + resource "radarr_notification_join" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + device_names = "test,test1" + api_key = "Key" + priority = %d + }`, name, priority) +} diff --git a/internal/provider/notification_kodi_resource.go b/internal/provider/notification_kodi_resource.go new file mode 100644 index 00000000..ac2548d1 --- /dev/null +++ b/internal/provider/notification_kodi_resource.go @@ -0,0 +1,423 @@ +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 ( + notificationKodiResourceName = "notification_kodi" + NotificationKodiImplementation = "Xbmc" + NotificationKodiConfigContrat = "XbmcSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationKodiResource{} +var _ resource.ResourceWithImportState = &NotificationKodiResource{} + +func NewNotificationKodiResource() resource.Resource { + return &NotificationKodiResource{} +} + +// NotificationKodiResource defines the notification implementation. +type NotificationKodiResource struct { + client *radarr.Radarr +} + +// NotificationKodi describes the notification data model. +type NotificationKodi struct { + Tags types.Set `tfsdk:"tags"` + Host types.String `tfsdk:"host"` + Name types.String `tfsdk:"name"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + DisplayTime types.Int64 `tfsdk:"display_time"` + Port types.Int64 `tfsdk:"port"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + UseSSL types.Bool `tfsdk:"use_ssl"` + Notify types.Bool `tfsdk:"notify"` + UpdateLibrary types.Bool `tfsdk:"update_library"` + CleanLibrary types.Bool `tfsdk:"clean_library"` + AlwaysUpdate types.Bool `tfsdk:"always_update"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + 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 NotificationKodi) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Port: n.Port, + Host: n.Host, + DisplayTime: n.DisplayTime, + Password: n.Password, + Username: n.Username, + Name: n.Name, + ID: n.ID, + UseSSL: n.UseSSL, + Notify: n.Notify, + UpdateLibrary: n.UpdateLibrary, + AlwaysUpdate: n.AlwaysUpdate, + CleanLibrary: n.CleanLibrary, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnRename: n.OnRename, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationKodi) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Port = notification.Port + n.DisplayTime = notification.DisplayTime + n.Host = notification.Host + n.Password = notification.Password + n.Username = notification.Username + n.Name = notification.Name + n.ID = notification.ID + n.UseSSL = notification.UseSSL + n.Notify = notification.Notify + n.UpdateLibrary = notification.UpdateLibrary + n.AlwaysUpdate = notification.AlwaysUpdate + n.CleanLibrary = notification.CleanLibrary + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnRename = notification.OnRename + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationKodiResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationKodiResourceName +} + +func (r *NotificationKodiResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Kodi resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Kodi](https://wiki.servarr.com/radarr/supported#xbmc).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationKodi 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 + "use_ssl": schema.BoolAttribute{ + MarkdownDescription: "Use SSL flag.", + Optional: true, + Computed: true, + }, + "notify": schema.BoolAttribute{ + MarkdownDescription: "Notification flag.", + Optional: true, + Computed: true, + }, + "update_library": schema.BoolAttribute{ + MarkdownDescription: "Update library flag.", + Optional: true, + Computed: true, + }, + "clean_library": schema.BoolAttribute{ + MarkdownDescription: "Clean library flag.", + Optional: true, + Computed: true, + }, + "always_update": schema.BoolAttribute{ + MarkdownDescription: "Always update flag.", + Optional: true, + Computed: true, + }, + "display_time": schema.Int64Attribute{ + MarkdownDescription: "Display time.", + Optional: true, + Computed: true, + }, + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", + Required: true, + }, + "host": schema.StringAttribute{ + MarkdownDescription: "Host.", + Required: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Optional: true, + Computed: true, + }, + "password": schema.StringAttribute{ + MarkdownDescription: "Password.", + Optional: true, + Computed: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationKodiResource) 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 *NotificationKodiResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationKodi + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationKodi + 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", notificationKodiResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationKodiResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationKodiResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationKodi + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationKodi 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", notificationKodiResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationKodiResourceName+": "+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 *NotificationKodiResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationKodi + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationKodi + 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", notificationKodiResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationKodiResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationKodiResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationKodi + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationKodi 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", notificationKodiResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationKodiResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationKodiResource) 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 "+notificationKodiResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationKodi) 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 *NotificationKodi) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + 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(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationKodiConfigContrat, + Implementation: NotificationKodiImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_kodi_resource_test.go b/internal/provider/notification_kodi_resource_test.go new file mode 100644 index 00000000..fa6451e9 --- /dev/null +++ b/internal/provider/notification_kodi_resource_test.go @@ -0,0 +1,66 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationKodiResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationKodiResourceConfig("resourceKodiTest", "pass1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_kodi.test", "password", "pass1"), + resource.TestCheckResourceAttrSet("radarr_notification_kodi.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationKodiResourceConfig("resourceKodiTest", "pass2"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_kodi.test", "password", "pass2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_kodi.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationKodiResourceConfig(name, avatar string) string { + return fmt.Sprintf(` + resource "radarr_notification_kodi" "test" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + host = "http://kodi.com" + port = 8080 + username = "User" + password = "%s" + notify = true + }`, name, avatar) +} diff --git a/internal/provider/notification_mailgun_resource.go b/internal/provider/notification_mailgun_resource.go new file mode 100644 index 00000000..ea454f4b --- /dev/null +++ b/internal/provider/notification_mailgun_resource.go @@ -0,0 +1,375 @@ +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 ( + notificationMailgunResourceName = "notification_mailgun" + NotificationMailgunImplementation = "Mailgun" + NotificationMailgunConfigContrat = "MailgunSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationMailgunResource{} +var _ resource.ResourceWithImportState = &NotificationMailgunResource{} + +func NewNotificationMailgunResource() resource.Resource { + return &NotificationMailgunResource{} +} + +// NotificationMailgunResource defines the notification implementation. +type NotificationMailgunResource struct { + client *radarr.Radarr +} + +// NotificationMailgun describes the notification data model. +type NotificationMailgun struct { + Tags types.Set `tfsdk:"tags"` + Recipients types.Set `tfsdk:"recipients"` + From types.String `tfsdk:"from"` + SenderDomain types.String `tfsdk:"sender_domain"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + ID types.Int64 `tfsdk:"id"` + UseEuEndpoint types.Bool `tfsdk:"use_eu_endpoint"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationMailgun) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Recipients: n.Recipients, + SenderDomain: n.SenderDomain, + APIKey: n.APIKey, + UseEuEndpoint: n.UseEuEndpoint, + Name: n.Name, + From: n.From, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationMailgun) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Recipients = notification.Recipients + n.SenderDomain = notification.SenderDomain + n.APIKey = notification.APIKey + n.UseEuEndpoint = notification.UseEuEndpoint + n.Name = notification.Name + n.From = notification.From + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationMailgunResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationMailgunResourceName +} + +func (r *NotificationMailgunResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Mailgun resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Mailgun](https://wiki.servarr.com/radarr/supported#mailgun).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationMailgun 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 + "use_eu_endpoint": schema.BoolAttribute{ + MarkdownDescription: "Use EU endpoint flag.", + Optional: true, + Computed: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "from": schema.StringAttribute{ + MarkdownDescription: "From.", + Required: true, + }, + "sender_domain": schema.StringAttribute{ + MarkdownDescription: "Sender domain.", + Optional: true, + Computed: true, + }, + "recipients": schema.SetAttribute{ + MarkdownDescription: "Recipients.", + Required: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationMailgunResource) 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 *NotificationMailgunResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationMailgun + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationMailgun + 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", notificationMailgunResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationMailgunResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationMailgunResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationMailgun + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationMailgun 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", notificationMailgunResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationMailgunResourceName+": "+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 *NotificationMailgunResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationMailgun + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationMailgun + 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", notificationMailgunResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationMailgunResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationMailgunResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationMailgun + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationMailgun 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", notificationMailgunResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationMailgunResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationMailgunResource) 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 "+notificationMailgunResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationMailgun) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationMailgun) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationMailgunConfigContrat, + Implementation: NotificationMailgunImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_mailgun_resource_test.go b/internal/provider/notification_mailgun_resource_test.go new file mode 100644 index 00000000..0d15f1c1 --- /dev/null +++ b/internal/provider/notification_mailgun_resource_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationMailgunResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationMailgunResourceConfig("resourceMailgunTest", "test@mailgun.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_mailgun.test", "from", "test@mailgun.com"), + resource.TestCheckResourceAttrSet("radarr_notification_mailgun.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationMailgunResourceConfig("resourceMailgunTest", "test123@mailgun.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_mailgun.test", "from", "test123@mailgun.com"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_mailgun.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationMailgunResourceConfig(name, from string) string { + return fmt.Sprintf(` + resource "radarr_notification_mailgun" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "APIkey" + from = "%s" + recipients = ["test@test.com", "test1@test.com"] + }`, name, from) +} diff --git a/internal/provider/notification_notifiarr_resource.go b/internal/provider/notification_notifiarr_resource.go new file mode 100644 index 00000000..36af3066 --- /dev/null +++ b/internal/provider/notification_notifiarr_resource.go @@ -0,0 +1,351 @@ +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 ( + notificationNotifiarrResourceName = "notification_notifiarr" + NotificationNotifiarrImplementation = "Notifiarr" + NotificationNotifiarrConfigContrat = "NotifiarrSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationNotifiarrResource{} +var _ resource.ResourceWithImportState = &NotificationNotifiarrResource{} + +func NewNotificationNotifiarrResource() resource.Resource { + return &NotificationNotifiarrResource{} +} + +// NotificationNotifiarrResource defines the notification implementation. +type NotificationNotifiarrResource struct { + client *radarr.Radarr +} + +// NotificationNotifiarr describes the notification data model. +type NotificationNotifiarr struct { + Tags types.Set `tfsdk:"tags"` + InstanceName types.String `tfsdk:"instance_name"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationNotifiarr) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + InstanceName: n.InstanceName, + APIKey: n.APIKey, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationNotifiarr) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.InstanceName = notification.InstanceName + n.APIKey = notification.APIKey + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationNotifiarrResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationNotifiarrResourceName +} + +func (r *NotificationNotifiarrResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Notifiarr resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Notifiarr](https://wiki.servarr.com/radarr/supported#notifiarr).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationNotifiarr 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 + "instance_name": schema.StringAttribute{ + MarkdownDescription: "Instance Name.", + Optional: true, + Computed: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationNotifiarrResource) 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 *NotificationNotifiarrResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationNotifiarr + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationNotifiarr + 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", notificationNotifiarrResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationNotifiarrResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationNotifiarrResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationNotifiarr + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationNotifiarr 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", notificationNotifiarrResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationNotifiarrResourceName+": "+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 *NotificationNotifiarrResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationNotifiarr + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationNotifiarr + 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", notificationNotifiarrResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationNotifiarrResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationNotifiarrResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationNotifiarr + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationNotifiarr 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", notificationNotifiarrResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationNotifiarrResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationNotifiarrResource) 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 "+notificationNotifiarrResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationNotifiarr) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationNotifiarr) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationNotifiarrConfigContrat, + Implementation: NotificationNotifiarrImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_notifiarr_resource_test.go b/internal/provider/notification_notifiarr_resource_test.go new file mode 100644 index 00000000..0c3be58c --- /dev/null +++ b/internal/provider/notification_notifiarr_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationNotifiarrResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationNotifiarrResourceConfig("resourceNotifiarrTest", "key1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_notifiarr.test", "api_key", "key1"), + resource.TestCheckResourceAttrSet("radarr_notification_notifiarr.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationNotifiarrResourceConfig("resourceNotifiarrTest", "key2"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_notifiarr.test", "api_key", "key2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_notifiarr.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationNotifiarrResourceConfig(name, key string) string { + return fmt.Sprintf(` + resource "radarr_notification_notifiarr" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "%s" + instance_name = "radarr" + }`, name, key) +} diff --git a/internal/provider/notification_ntfy_resource.go b/internal/provider/notification_ntfy_resource.go new file mode 100644 index 00000000..dcfb45bf --- /dev/null +++ b/internal/provider/notification_ntfy_resource.go @@ -0,0 +1,398 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema/validator" + "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 ( + notificationNtfyResourceName = "notification_ntfy" + NotificationNtfyImplementation = "Ntfy" + NotificationNtfyConfigContrat = "NtfySettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationNtfyResource{} +var _ resource.ResourceWithImportState = &NotificationNtfyResource{} + +func NewNotificationNtfyResource() resource.Resource { + return &NotificationNtfyResource{} +} + +// NotificationNtfyResource defines the notification implementation. +type NotificationNtfyResource struct { + client *radarr.Radarr +} + +// NotificationNtfy describes the notification data model. +type NotificationNtfy struct { + Tags types.Set `tfsdk:"tags"` + FieldTags types.Set `tfsdk:"field_tags"` + Topics types.Set `tfsdk:"topics"` + ClickURL types.String `tfsdk:"click_url"` + ServerURL types.String `tfsdk:"server_url"` + Username types.String `tfsdk:"username"` + Name types.String `tfsdk:"name"` + Password types.String `tfsdk:"password"` + Priority types.Int64 `tfsdk:"priority"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationNtfy) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + FieldTags: n.FieldTags, + Topics: n.Topics, + ServerURL: n.ServerURL, + ClickURL: n.ClickURL, + Username: n.Username, + Password: n.Password, + Name: n.Name, + Priority: n.Priority, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationNtfy) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.FieldTags = notification.FieldTags + n.Topics = notification.Topics + n.ServerURL = notification.ServerURL + n.ClickURL = notification.ClickURL + n.Username = notification.Username + n.Password = notification.Password + n.Name = notification.Name + n.Priority = notification.Priority + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationNtfyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationNtfyResourceName +} + +func (r *NotificationNtfyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Ntfy resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Ntfy](https://wiki.servarr.com/radarr/supported#ntfy).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationNtfy 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 + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority. `1` Min, `2` Low, `3` Default, `4` High, `5` Max.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(1, 2, 3, 4, 5), + }, + }, + "server_url": schema.StringAttribute{ + MarkdownDescription: "Server URL.", + Optional: true, + Computed: true, + }, + "click_url": schema.StringAttribute{ + MarkdownDescription: "Click URL.", + Optional: true, + Computed: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Optional: true, + Computed: true, + }, + "password": schema.StringAttribute{ + MarkdownDescription: "Password.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "topics": schema.SetAttribute{ + MarkdownDescription: "Topics.", + Required: true, + ElementType: types.StringType, + }, + "field_tags": schema.SetAttribute{ + MarkdownDescription: "Tags and emojis.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationNtfyResource) 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 *NotificationNtfyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationNtfy + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationNtfy + 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", notificationNtfyResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationNtfyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationNtfyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationNtfy + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationNtfy 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", notificationNtfyResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationNtfyResourceName+": "+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 *NotificationNtfyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationNtfy + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationNtfy + 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", notificationNtfyResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationNtfyResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationNtfyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationNtfy + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationNtfy 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", notificationNtfyResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationNtfyResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationNtfyResource) 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 "+notificationNtfyResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationNtfy) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationNtfy) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationNtfyConfigContrat, + Implementation: NotificationNtfyImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_ntfy_resource_test.go b/internal/provider/notification_ntfy_resource_test.go new file mode 100644 index 00000000..2b457d0f --- /dev/null +++ b/internal/provider/notification_ntfy_resource_test.go @@ -0,0 +1,66 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationNtfyResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationNtfyResourceConfig("resourceNtfyTest", "key1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_ntfy.test", "password", "key1"), + resource.TestCheckResourceAttrSet("radarr_notification_ntfy.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationNtfyResourceConfig("resourceNtfyTest", "key2"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_ntfy.test", "password", "key2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_ntfy.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationNtfyResourceConfig(name, key string) string { + return fmt.Sprintf(` + resource "radarr_notification_ntfy" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + priority = 1 + server_url = "https://ntfy.sh" + username = "User" + password = "%s" + topics = ["Topic1234","Topic4321"] + field_tags = ["warning","skull"] + }`, name, key) +} diff --git a/internal/provider/notification_plex_resource.go b/internal/provider/notification_plex_resource.go new file mode 100644 index 00000000..95a6c6ac --- /dev/null +++ b/internal/provider/notification_plex_resource.go @@ -0,0 +1,359 @@ +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 ( + notificationPlexResourceName = "notification_plex" + NotificationPlexImplementation = "PlexServer" + NotificationPlexConfigContrat = "PlexServerSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationPlexResource{} +var _ resource.ResourceWithImportState = &NotificationPlexResource{} + +func NewNotificationPlexResource() resource.Resource { + return &NotificationPlexResource{} +} + +// NotificationPlexResource defines the notification implementation. +type NotificationPlexResource struct { + client *radarr.Radarr +} + +// NotificationPlex describes the notification data model. +type NotificationPlex struct { + Tags types.Set `tfsdk:"tags"` + Host types.String `tfsdk:"host"` + AuthToken types.String `tfsdk:"auth_token"` + Name types.String `tfsdk:"name"` + ID types.Int64 `tfsdk:"id"` + Port types.Int64 `tfsdk:"port"` + UpdateLibrary types.Bool `tfsdk:"update_library"` + UseSSL types.Bool `tfsdk:"use_ssl"` + 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 NotificationPlex) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Host: n.Host, + Name: n.Name, + AuthToken: n.AuthToken, + ID: n.ID, + Port: n.Port, + UpdateLibrary: n.UpdateLibrary, + UseSSL: n.UseSSL, + 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 *NotificationPlex) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Host = notification.Host + n.Name = notification.Name + n.AuthToken = notification.AuthToken + n.ID = notification.ID + n.UpdateLibrary = notification.UpdateLibrary + n.Port = notification.Port + n.UseSSL = notification.UseSSL + 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 *NotificationPlexResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationPlexResourceName +} + +func (r *NotificationPlexResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Plex resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Plex](https://wiki.servarr.com/radarr/supported#plexserver).", + 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: "NotificationPlex 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 + "use_ssl": schema.BoolAttribute{ + MarkdownDescription: "Use SSL flag.", + Optional: true, + Computed: true, + }, + "update_library": schema.BoolAttribute{ + MarkdownDescription: "Update library flag.", + Optional: true, + Computed: true, + }, + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", + Optional: true, + Computed: true, + }, + "auth_token": schema.StringAttribute{ + MarkdownDescription: "Auth Token.", + Required: true, + Sensitive: true, + }, + "host": schema.StringAttribute{ + MarkdownDescription: "Host.", + Required: true, + }, + }, + } +} + +func (r *NotificationPlexResource) 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 *NotificationPlexResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationPlex + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationPlex + 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", notificationPlexResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationPlexResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPlexResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationPlex + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationPlex 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", notificationPlexResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationPlexResourceName+": "+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 *NotificationPlexResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationPlex + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationPlex + 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", notificationPlexResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationPlexResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPlexResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationPlex + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationPlex 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", notificationPlexResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationPlexResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationPlexResource) 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 "+notificationPlexResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationPlex) 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 *NotificationPlex) 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: NotificationPlexConfigContrat, + Implementation: NotificationPlexImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_plex_resource_test.go b/internal/provider/notification_plex_resource_test.go new file mode 100644 index 00000000..e702df15 --- /dev/null +++ b/internal/provider/notification_plex_resource_test.go @@ -0,0 +1,61 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationPlexResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationPlexResourceConfig("resourcePlexTest", "token123"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_plex.test", "auth_token", "token123"), + resource.TestCheckResourceAttrSet("radarr_notification_plex.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationPlexResourceConfig("resourcePlexTest", "token234"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_plex.test", "auth_token", "token234"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_plex.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationPlexResourceConfig(name, token string) string { + return fmt.Sprintf(` + resource "radarr_notification_plex" "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" + + host = "plex.lcl" + port = 32400 + auth_token = "%s" + }`, name, token) +} diff --git a/internal/provider/notification_prowl_resource.go b/internal/provider/notification_prowl_resource.go new file mode 100644 index 00000000..6c20aed7 --- /dev/null +++ b/internal/provider/notification_prowl_resource.go @@ -0,0 +1,356 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema/validator" + "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 ( + notificationProwlResourceName = "notification_prowl" + NotificationProwlImplementation = "Prowl" + NotificationProwlConfigContrat = "ProwlSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationProwlResource{} +var _ resource.ResourceWithImportState = &NotificationProwlResource{} + +func NewNotificationProwlResource() resource.Resource { + return &NotificationProwlResource{} +} + +// NotificationProwlResource defines the notification implementation. +type NotificationProwlResource struct { + client *radarr.Radarr +} + +// NotificationProwl describes the notification data model. +type NotificationProwl struct { + Tags types.Set `tfsdk:"tags"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + Priority types.Int64 `tfsdk:"priority"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationProwl) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + APIKey: n.APIKey, + Priority: n.Priority, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationProwl) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.APIKey = notification.APIKey + n.Priority = notification.Priority + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationProwlResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationProwlResourceName +} + +func (r *NotificationProwlResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Prowl resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Prowl](https://wiki.servarr.com/radarr/supported#prowl).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationProwl 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 + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority.`-2` Very Low, `-1` Low, `0` Normal, `1` High, `2` Emergency.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(-2, -1, 0, 1, 2), + }, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationProwlResource) 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 *NotificationProwlResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationProwl + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationProwl + 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", notificationProwlResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationProwlResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationProwlResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationProwl + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationProwl 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", notificationProwlResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationProwlResourceName+": "+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 *NotificationProwlResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationProwl + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationProwl + 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", notificationProwlResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationProwlResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationProwlResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationProwl + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationProwl 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", notificationProwlResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationProwlResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationProwlResource) 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 "+notificationProwlResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationProwl) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationProwl) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationProwlConfigContrat, + Implementation: NotificationProwlImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_prowl_resource_test.go b/internal/provider/notification_prowl_resource_test.go new file mode 100644 index 00000000..fab56c5a --- /dev/null +++ b/internal/provider/notification_prowl_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationProwlResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationProwlResourceConfig("resourceProwlTest", 0), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_prowl.test", "priority", "0"), + resource.TestCheckResourceAttrSet("radarr_notification_prowl.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationProwlResourceConfig("resourceProwlTest", 2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_prowl.test", "priority", "2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_prowl.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationProwlResourceConfig(name string, priority int) string { + return fmt.Sprintf(` + resource "radarr_notification_prowl" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "Key" + priority = %d + }`, name, priority) +} diff --git a/internal/provider/notification_pushbullet_resource.go b/internal/provider/notification_pushbullet_resource.go new file mode 100644 index 00000000..fc496f86 --- /dev/null +++ b/internal/provider/notification_pushbullet_resource.go @@ -0,0 +1,369 @@ +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 ( + notificationPushbulletResourceName = "notification_pushbullet" + NotificationPushbulletImplementation = "PushBullet" + NotificationPushbulletConfigContrat = "PushBulletSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationPushbulletResource{} +var _ resource.ResourceWithImportState = &NotificationPushbulletResource{} + +func NewNotificationPushbulletResource() resource.Resource { + return &NotificationPushbulletResource{} +} + +// NotificationPushbulletResource defines the notification implementation. +type NotificationPushbulletResource struct { + client *radarr.Radarr +} + +// NotificationPushbullet describes the notification data model. +type NotificationPushbullet struct { + Tags types.Set `tfsdk:"tags"` + DeviceIds types.Set `tfsdk:"device_ids"` + ChannelTags types.Set `tfsdk:"channel_tags"` + SenderID types.String `tfsdk:"sender_id"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationPushbullet) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + DeviceIds: n.DeviceIds, + ChannelTags: n.ChannelTags, + SenderID: n.SenderID, + APIKey: n.APIKey, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationPushbullet) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.DeviceIds = notification.DeviceIds + n.ChannelTags = notification.ChannelTags + n.SenderID = notification.SenderID + n.APIKey = notification.APIKey + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationPushbulletResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationPushbulletResourceName +} + +func (r *NotificationPushbulletResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Pushbullet resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Pushbullet](https://wiki.servarr.com/radarr/supported#pushbullet).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationPushbullet 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 + "sender_id": schema.StringAttribute{ + MarkdownDescription: "Sender ID.", + Optional: true, + Computed: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Required: true, + Sensitive: true, + }, + "device_ids": schema.SetAttribute{ + MarkdownDescription: "List of devices IDs.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "channel_tags": schema.SetAttribute{ + MarkdownDescription: "List of channel tags.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationPushbulletResource) 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 *NotificationPushbulletResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationPushbullet + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationPushbullet + 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", notificationPushbulletResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationPushbulletResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPushbulletResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationPushbullet + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationPushbullet 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", notificationPushbulletResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationPushbulletResourceName+": "+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 *NotificationPushbulletResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationPushbullet + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationPushbullet + 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", notificationPushbulletResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationPushbulletResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPushbulletResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationPushbullet + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationPushbullet 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", notificationPushbulletResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationPushbulletResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationPushbulletResource) 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 "+notificationPushbulletResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationPushbullet) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationPushbullet) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationPushbulletConfigContrat, + Implementation: NotificationPushbulletImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_pushbullet_resource_test.go b/internal/provider/notification_pushbullet_resource_test.go new file mode 100644 index 00000000..545b60bf --- /dev/null +++ b/internal/provider/notification_pushbullet_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationPushbulletResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationPushbulletResourceConfig("resourcePushbulletTest", "key1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_pushbullet.test", "api_key", "key1"), + resource.TestCheckResourceAttrSet("radarr_notification_pushbullet.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationPushbulletResourceConfig("resourcePushbulletTest", "key2"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_pushbullet.test", "api_key", "key2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_pushbullet.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationPushbulletResourceConfig(name, key string) string { + return fmt.Sprintf(` + resource "radarr_notification_pushbullet" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "%s" + device_ids = ["test"] + }`, name, key) +} diff --git a/internal/provider/notification_pushover_resource.go b/internal/provider/notification_pushover_resource.go new file mode 100644 index 00000000..cb51367a --- /dev/null +++ b/internal/provider/notification_pushover_resource.go @@ -0,0 +1,397 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + + "github.com/devopsarr/terraform-provider-sonarr/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/schema/validator" + "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 ( + notificationPushoverResourceName = "notification_pushover" + NotificationPushoverImplementation = "Pushover" + NotificationPushoverConfigContrat = "PushoverSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationPushoverResource{} +var _ resource.ResourceWithImportState = &NotificationPushoverResource{} + +func NewNotificationPushoverResource() resource.Resource { + return &NotificationPushoverResource{} +} + +// NotificationPushoverResource defines the notification implementation. +type NotificationPushoverResource struct { + client *radarr.Radarr +} + +// NotificationPushover describes the notification data model. +type NotificationPushover struct { + Tags types.Set `tfsdk:"tags"` + Devices types.Set `tfsdk:"devices"` + Sound types.String `tfsdk:"sound"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + UserKey types.String `tfsdk:"user_key"` + Priority types.Int64 `tfsdk:"priority"` + ID types.Int64 `tfsdk:"id"` + Retry types.Int64 `tfsdk:"retry"` + Expire types.Int64 `tfsdk:"expire"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationPushover) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Devices: n.Devices, + Sound: n.Sound, + APIKey: n.APIKey, + UserKey: n.UserKey, + Retry: n.Retry, + Expire: n.Expire, + Priority: n.Priority, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationPushover) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Devices = notification.Devices + n.Sound = notification.Sound + n.APIKey = notification.APIKey + n.UserKey = notification.UserKey + n.Retry = notification.Retry + n.Expire = notification.Expire + n.Priority = notification.Priority + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationPushoverResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationPushoverResourceName +} + +func (r *NotificationPushoverResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Pushover resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Pushover](https://wiki.servarr.com/radarr/supported#pushover).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationPushover 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 + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority. `-2` Silent, `-1` Quiet, `0` Normal, `1` High, `2` Emergency, `8` High.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(-1, -2, 0, 1, 2), + }, + }, + "retry": schema.Int64Attribute{ + MarkdownDescription: "Retry.", + Optional: true, + Computed: true, + }, + "expire": schema.Int64Attribute{ + MarkdownDescription: "Expire.", + Optional: true, + Computed: true, + }, + "sound": schema.StringAttribute{ + MarkdownDescription: "Sound.", + Optional: true, + Computed: true, + }, + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Required: true, + Sensitive: true, + }, + "user_key": schema.StringAttribute{ + MarkdownDescription: "User key.", + Optional: true, + Sensitive: true, + }, + "devices": schema.SetAttribute{ + MarkdownDescription: "List of devices.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationPushoverResource) 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 *NotificationPushoverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationPushover + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationPushover + 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", notificationPushoverResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationPushoverResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPushoverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationPushover + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationPushover 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", notificationPushoverResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationPushoverResourceName+": "+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 *NotificationPushoverResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationPushover + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationPushover + 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", notificationPushoverResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationPushoverResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationPushoverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationPushover + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationPushover 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", notificationPushoverResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationPushoverResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationPushoverResource) 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 "+notificationPushoverResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationPushover) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationPushover) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationPushoverConfigContrat, + Implementation: NotificationPushoverImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_pushover_resource_test.go b/internal/provider/notification_pushover_resource_test.go new file mode 100644 index 00000000..86590744 --- /dev/null +++ b/internal/provider/notification_pushover_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationPushoverResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationPushoverResourceConfig("resourcePushoverTest", 0), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_pushover.test", "priority", "0"), + resource.TestCheckResourceAttrSet("radarr_notification_pushover.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationPushoverResourceConfig("resourcePushoverTest", 2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_pushover.test", "priority", "2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_pushover.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationPushoverResourceConfig(name string, priority int) string { + return fmt.Sprintf(` + resource "radarr_notification_pushover" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "Key" + priority = %d + }`, name, priority) +} diff --git a/internal/provider/notification_resource.go b/internal/provider/notification_resource.go index f68dd7a6..234d7e1f 100644 --- a/internal/provider/notification_resource.go +++ b/internal/provider/notification_resource.go @@ -29,10 +29,10 @@ var _ resource.ResourceWithImportState = &NotificationResource{} var ( notificationBoolFields = []string{"alwaysUpdate", "cleanLibrary", "directMessage", "notify", "requireEncryption", "sendSilently", "useSsl", "updateLibrary", "useEuEndpoint"} - notificationStringFields = []string{"accessToken", "accessTokenSecret", "apiKey", "aPIKey", "appToken", "arguments", "author", "authToken", "authUser", "avatar", "cc", "bcc", "botToken", "channel", "chatId", "consumerKey", "consumerSecret", "deviceNames", "displayTime", "expires", "from", "host", "icon", "instanceName", "mention", "password", "path", "refreshToken", "senderDomain", "senderId", "server", "signIn", "sound", "to", "token", "url", "userKey", "username", "webHookUrl", "serverUrl", "userName", "clickUrl", "mapFrom", "mapTo", "key", "event"} - notificationIntFields = []string{"port", "grabFields", "importFields", "priority", "retry", "expire", "method"} - notificationStringSliceFields = []string{"recipients", "topics", "tags", "channelTags", "devices"} - notificationIntSliceFields = []string{"deviceIds"} + notificationStringFields = []string{"accessToken", "accessTokenSecret", "apiKey", "aPIKey", "appToken", "arguments", "author", "authToken", "authUser", "avatar", "botToken", "channel", "chatId", "consumerKey", "consumerSecret", "deviceNames", "expires", "from", "host", "icon", "instanceName", "mention", "password", "path", "refreshToken", "senderDomain", "senderId", "server", "signIn", "sound", "token", "url", "userKey", "username", "webHookUrl", "serverUrl", "userName", "clickUrl", "mapFrom", "mapTo", "key", "event"} + notificationIntFields = []string{"displayTime", "port", "priority", "retry", "expire", "method"} + notificationStringSliceFields = []string{"recipients", "to", "cC", "bcc", "topics", "deviceIds", "fieldTags", "channelTags", "devices"} + notificationIntSliceFields = []string{"grabFields", "importFields"} ) func NewNotificationResource() resource.Resource { @@ -50,8 +50,13 @@ type Notification struct { FieldTags types.Set `tfsdk:"field_tags"` ChannelTags types.Set `tfsdk:"channel_tags"` Topics types.Set `tfsdk:"topics"` + ImportFields types.Set `tfsdk:"import_fields"` + GrabFields types.Set `tfsdk:"grab_fields"` DeviceIds types.Set `tfsdk:"device_ids"` Devices types.Set `tfsdk:"devices"` + To types.Set `tfsdk:"to"` + Cc types.Set `tfsdk:"cc"` + Bcc types.Set `tfsdk:"bcc"` Recipients types.Set `tfsdk:"recipients"` DeviceNames types.String `tfsdk:"device_names"` AccessToken types.String `tfsdk:"access_token"` @@ -67,12 +72,10 @@ type Notification struct { ConsumerKey types.String `tfsdk:"consumer_key"` ChatID types.String `tfsdk:"chat_id"` From types.String `tfsdk:"from"` - Cc types.String `tfsdk:"cc"` Icon types.String `tfsdk:"icon"` Password types.String `tfsdk:"password"` Event types.String `tfsdk:"event"` Key types.String `tfsdk:"key"` - DisplayTime types.String `tfsdk:"display_time"` RefreshToken types.String `tfsdk:"refresh_token"` WebHookURL types.String `tfsdk:"web_hook_url"` Username types.String `tfsdk:"username"` @@ -81,9 +84,7 @@ type Notification struct { Avatar types.String `tfsdk:"avatar"` URL types.String `tfsdk:"url"` Token types.String `tfsdk:"token"` - To types.String `tfsdk:"to"` Sound types.String `tfsdk:"sound"` - Bcc types.String `tfsdk:"bcc"` SignIn types.String `tfsdk:"sign_in"` Server types.String `tfsdk:"server"` SenderID types.String `tfsdk:"sender_id"` @@ -100,14 +101,13 @@ type Notification struct { Author types.String `tfsdk:"author"` AuthToken types.String `tfsdk:"auth_token"` AuthUser types.String `tfsdk:"auth_user"` + DisplayTime types.Int64 `tfsdk:"display_time"` Priority types.Int64 `tfsdk:"priority"` Port types.Int64 `tfsdk:"port"` Method types.Int64 `tfsdk:"method"` Retry types.Int64 `tfsdk:"retry"` Expire types.Int64 `tfsdk:"expire"` ID types.Int64 `tfsdk:"id"` - ImportFields types.Int64 `tfsdk:"import_fields"` - GrabFields types.Int64 `tfsdk:"grab_fields"` CleanLibrary types.Bool `tfsdk:"clean_library"` OnGrab types.Bool `tfsdk:"on_grab"` OnMovieAdded types.Bool `tfsdk:"on_movie_added"` @@ -253,26 +253,15 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe Optional: true, Computed: true, }, - "port": schema.Int64Attribute{ - MarkdownDescription: "Port.", - Optional: true, - Computed: true, - }, - "grab_fields": schema.Int64Attribute{ - MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore.", + "display_time": schema.Int64Attribute{ + MarkdownDescription: "Display time.", Optional: true, Computed: true, - Validators: []validator.Int64{ - int64validator.OneOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), - }, }, - "import_fields": schema.Int64Attribute{ - MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", Optional: true, Computed: true, - Validators: []validator.Int64{ - int64validator.OneOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), - }, }, "method": schema.Int64Attribute{ MarkdownDescription: "Method. `1` POST, `2` PUT.", @@ -350,21 +339,11 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe Optional: true, Computed: true, }, - "bcc": schema.StringAttribute{ - MarkdownDescription: "Bcc.", - Optional: true, - Computed: true, - }, "bot_token": schema.StringAttribute{ MarkdownDescription: "Bot token.", Optional: true, Computed: true, }, - "cc": schema.StringAttribute{ - MarkdownDescription: "Cc.", - Optional: true, - Computed: true, - }, "channel": schema.StringAttribute{ MarkdownDescription: "Channel.", Optional: true, @@ -390,11 +369,6 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe Optional: true, Computed: true, }, - "display_time": schema.StringAttribute{ - MarkdownDescription: "Display time.", - Optional: true, - Computed: true, - }, "expires": schema.StringAttribute{ MarkdownDescription: "Expires.", Optional: true, @@ -460,11 +434,6 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe Optional: true, Computed: true, }, - "to": schema.StringAttribute{ - MarkdownDescription: "To.", - Optional: true, - Computed: true, - }, "token": schema.StringAttribute{ MarkdownDescription: "Token.", Optional: true, @@ -524,7 +493,7 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: "Device IDs.", Optional: true, Computed: true, - ElementType: types.Int64Type, + ElementType: types.StringType, }, "channel_tags": schema.SetAttribute{ MarkdownDescription: "Channel tags.", @@ -539,13 +508,25 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe ElementType: types.StringType, }, "topics": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Topics.", Optional: true, Computed: true, ElementType: types.StringType, }, + "grab_fields": schema.SetAttribute{ + MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + "import_fields": schema.SetAttribute{ + MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, "field_tags": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Specific tags.", Optional: true, Computed: true, ElementType: types.StringType, @@ -556,6 +537,24 @@ func (r *NotificationResource) Schema(ctx context.Context, req resource.SchemaRe Computed: true, ElementType: types.StringType, }, + "to": schema.SetAttribute{ + MarkdownDescription: "To.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "cc": schema.SetAttribute{ + MarkdownDescription: "Cc.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "bcc": schema.SetAttribute{ + MarkdownDescription: "Bcc.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, }, } } @@ -717,13 +716,18 @@ func (n *Notification) write(ctx context.Context, notification *radarr.Notificat n.Name = types.StringValue(notification.Name) n.Implementation = types.StringValue(notification.Implementation) n.ConfigContract = types.StringValue(notification.ConfigContract) + n.GrabFields = types.SetValueMust(types.Int64Type, nil) + n.ImportFields = types.SetValueMust(types.Int64Type, nil) n.Tags = types.SetValueMust(types.Int64Type, nil) n.ChannelTags = types.SetValueMust(types.StringType, nil) - n.DeviceIds = types.SetValueMust(types.Int64Type, nil) + n.DeviceIds = types.SetValueMust(types.StringType, nil) n.Topics = types.SetValueMust(types.StringType, nil) n.Devices = types.SetValueMust(types.StringType, nil) n.Recipients = types.SetValueMust(types.StringType, nil) n.FieldTags = types.SetValueMust(types.StringType, nil) + n.To = types.SetValueMust(types.StringType, nil) + n.Cc = types.SetValueMust(types.StringType, nil) + n.Bcc = types.SetValueMust(types.StringType, nil) tfsdk.ValueFrom(ctx, notification.Tags, n.Tags.Type(ctx), &n.Tags) n.writeFields(ctx, notification.Fields) } @@ -752,7 +756,7 @@ func (n *Notification) writeFields(ctx context.Context, fields []*starr.FieldOut continue } - if slices.Contains(notificationStringSliceFields, f.Name) { + if slices.Contains(notificationStringSliceFields, f.Name) || f.Name == "tags" { tools.WriteStringSliceField(ctx, f, n) } diff --git a/internal/provider/notification_sendgrid_resource.go b/internal/provider/notification_sendgrid_resource.go new file mode 100644 index 00000000..30e4fb6f --- /dev/null +++ b/internal/provider/notification_sendgrid_resource.go @@ -0,0 +1,359 @@ +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 ( + notificationSendgridResourceName = "notification_sendgrid" + NotificationSendgridImplementation = "Sendgrid" + NotificationSendgridConfigContrat = "SendgridSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationSendgridResource{} +var _ resource.ResourceWithImportState = &NotificationSendgridResource{} + +func NewNotificationSendgridResource() resource.Resource { + return &NotificationSendgridResource{} +} + +// NotificationSendgridResource defines the notification implementation. +type NotificationSendgridResource struct { + client *radarr.Radarr +} + +// NotificationSendgrid describes the notification data model. +type NotificationSendgrid struct { + Tags types.Set `tfsdk:"tags"` + Recipients types.Set `tfsdk:"recipients"` + From types.String `tfsdk:"from"` + Name types.String `tfsdk:"name"` + APIKey types.String `tfsdk:"api_key"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationSendgrid) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Recipients: n.Recipients, + APIKey: n.APIKey, + Name: n.Name, + From: n.From, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationSendgrid) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Recipients = notification.Recipients + n.APIKey = notification.APIKey + n.Name = notification.Name + n.From = notification.From + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationSendgridResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationSendgridResourceName +} + +func (r *NotificationSendgridResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Sendgrid resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Sendgrid](https://wiki.servarr.com/radarr/supported#sendgrid).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationSendgrid 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 + "api_key": schema.StringAttribute{ + MarkdownDescription: "API key.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "from": schema.StringAttribute{ + MarkdownDescription: "From.", + Required: true, + }, + "recipients": schema.SetAttribute{ + MarkdownDescription: "Recipients.", + Required: true, + ElementType: types.StringType, + }, + }, + } +} + +func (r *NotificationSendgridResource) 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 *NotificationSendgridResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationSendgrid + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationSendgrid + 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", notificationSendgridResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationSendgridResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSendgridResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationSendgrid + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationSendgrid 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", notificationSendgridResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationSendgridResourceName+": "+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 *NotificationSendgridResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationSendgrid + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationSendgrid + 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", notificationSendgridResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationSendgridResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSendgridResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationSendgrid + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationSendgrid 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", notificationSendgridResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationSendgridResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationSendgridResource) 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 "+notificationSendgridResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationSendgrid) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationSendgrid) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationSendgridConfigContrat, + Implementation: NotificationSendgridImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_sendgrid_resource_test.go b/internal/provider/notification_sendgrid_resource_test.go new file mode 100644 index 00000000..e75fdddb --- /dev/null +++ b/internal/provider/notification_sendgrid_resource_test.go @@ -0,0 +1,63 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationSendgridResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationSendgridResourceConfig("resourceSendgridTest", "test@sendgrid.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_sendgrid.test", "from", "test@sendgrid.com"), + resource.TestCheckResourceAttrSet("radarr_notification_sendgrid.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationSendgridResourceConfig("resourceSendgridTest", "test123@sendgrid.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_sendgrid.test", "from", "test123@sendgrid.com"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_sendgrid.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationSendgridResourceConfig(name, from string) string { + return fmt.Sprintf(` + resource "radarr_notification_sendgrid" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + api_key = "APIkey" + from = "%s" + recipients = ["test@test.com", "test1@test.com"] + }`, name, from) +} diff --git a/internal/provider/notification_simplepush_resource.go b/internal/provider/notification_simplepush_resource.go new file mode 100644 index 00000000..0aad8b34 --- /dev/null +++ b/internal/provider/notification_simplepush_resource.go @@ -0,0 +1,351 @@ +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 ( + notificationSimplepushResourceName = "notification_simplepush" + NotificationSimplepushImplementation = "Simplepush" + NotificationSimplepushConfigContrat = "SimplepushSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationSimplepushResource{} +var _ resource.ResourceWithImportState = &NotificationSimplepushResource{} + +func NewNotificationSimplepushResource() resource.Resource { + return &NotificationSimplepushResource{} +} + +// NotificationSimplepushResource defines the notification implementation. +type NotificationSimplepushResource struct { + client *radarr.Radarr +} + +// NotificationSimplepush describes the notification data model. +type NotificationSimplepush struct { + Tags types.Set `tfsdk:"tags"` + Event types.String `tfsdk:"event"` + Name types.String `tfsdk:"name"` + Key types.String `tfsdk:"key"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationSimplepush) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + Event: n.Event, + Key: n.Key, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationSimplepush) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.Event = notification.Event + n.Key = notification.Key + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationSimplepushResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationSimplepushResourceName +} + +func (r *NotificationSimplepushResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Simplepush resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Simplepush](https://wiki.servarr.com/radarr/supported#simplepush).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationSimplepush 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 + "event": schema.StringAttribute{ + MarkdownDescription: "Event.", + Optional: true, + Computed: true, + }, + "key": schema.StringAttribute{ + MarkdownDescription: "Key.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationSimplepushResource) 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 *NotificationSimplepushResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationSimplepush + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationSimplepush + 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", notificationSimplepushResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationSimplepushResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSimplepushResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationSimplepush + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationSimplepush 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", notificationSimplepushResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationSimplepushResourceName+": "+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 *NotificationSimplepushResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationSimplepush + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationSimplepush + 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", notificationSimplepushResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationSimplepushResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSimplepushResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationSimplepush + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationSimplepush 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", notificationSimplepushResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationSimplepushResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationSimplepushResource) 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 "+notificationSimplepushResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationSimplepush) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationSimplepush) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationSimplepushConfigContrat, + Implementation: NotificationSimplepushImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_simplepush_resource_test.go b/internal/provider/notification_simplepush_resource_test.go new file mode 100644 index 00000000..0ef94de3 --- /dev/null +++ b/internal/provider/notification_simplepush_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationSimplepushResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationSimplepushResourceConfig("resourceSimplepushTest", "key1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_simplepush.test", "key", "key1"), + resource.TestCheckResourceAttrSet("radarr_notification_simplepush.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationSimplepushResourceConfig("resourceSimplepushTest", "key2"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_simplepush.test", "key", "key2"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_simplepush.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationSimplepushResourceConfig(name, key string) string { + return fmt.Sprintf(` + resource "radarr_notification_simplepush" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + key = "%s" + event = "ringtone:default" + }`, name, key) +} diff --git a/internal/provider/notification_slack_resource.go b/internal/provider/notification_slack_resource.go new file mode 100644 index 00000000..dc2241ae --- /dev/null +++ b/internal/provider/notification_slack_resource.go @@ -0,0 +1,374 @@ +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 ( + notificationSlackResourceName = "notification_slack" + NotificationSlackImplementation = "Slack" + NotificationSlackConfigContrat = "SlackSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationSlackResource{} +var _ resource.ResourceWithImportState = &NotificationSlackResource{} + +func NewNotificationSlackResource() resource.Resource { + return &NotificationSlackResource{} +} + +// NotificationSlackResource defines the notification implementation. +type NotificationSlackResource struct { + client *radarr.Radarr +} + +// NotificationSlack describes the notification data model. +type NotificationSlack struct { + Tags types.Set `tfsdk:"tags"` + WebHookURL types.String `tfsdk:"web_hook_url"` + Name types.String `tfsdk:"name"` + Username types.String `tfsdk:"username"` + Icon types.String `tfsdk:"icon"` + Channel types.String `tfsdk:"channel"` + ID types.Int64 `tfsdk:"id"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + 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 NotificationSlack) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + WebHookURL: n.WebHookURL, + Icon: n.Icon, + Username: n.Username, + Channel: n.Channel, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnRename: n.OnRename, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationSlack) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.WebHookURL = notification.WebHookURL + n.Icon = notification.Icon + n.Username = notification.Username + n.Channel = notification.Channel + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnRename = notification.OnRename + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationSlackResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationSlackResourceName +} + +func (r *NotificationSlackResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Slack resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Slack](https://wiki.servarr.com/radarr/supported#slack).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationSlack 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 + "web_hook_url": schema.StringAttribute{ + MarkdownDescription: "URL.", + Required: true, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Required: true, + }, + "icon": schema.StringAttribute{ + MarkdownDescription: "Icon.", + Optional: true, + Computed: true, + }, + "channel": schema.StringAttribute{ + MarkdownDescription: "Channel.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (r *NotificationSlackResource) 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 *NotificationSlackResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationSlack + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationSlack + 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", notificationSlackResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationSlackResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSlackResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationSlack + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationSlack 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", notificationSlackResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationSlackResourceName+": "+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 *NotificationSlackResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationSlack + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationSlack + 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", notificationSlackResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationSlackResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationSlackResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationSlack + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationSlack 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", notificationSlackResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationSlackResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationSlackResource) 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 "+notificationSlackResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationSlack) 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 *NotificationSlack) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + 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(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationSlackConfigContrat, + Implementation: NotificationSlackImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_slack_resource_test.go b/internal/provider/notification_slack_resource_test.go new file mode 100644 index 00000000..97f77591 --- /dev/null +++ b/internal/provider/notification_slack_resource_test.go @@ -0,0 +1,64 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationSlackResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationSlackResourceConfig("resourceSlackTest", "test"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_slack.test", "channel", "test"), + resource.TestCheckResourceAttrSet("radarr_notification_slack.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationSlackResourceConfig("resourceSlackTest", "test1"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_slack.test", "channel", "test1"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_slack.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationSlackResourceConfig(name, channel string) string { + return fmt.Sprintf(` + resource "radarr_notification_slack" "test" { + on_grab = false + 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 + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + web_hook_url = "http://my.slack.com/test" + username = "user" + channel = "%s" + }`, name, channel) +} 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/notification_telegram_resource.go b/internal/provider/notification_telegram_resource.go new file mode 100644 index 00000000..e96e59a2 --- /dev/null +++ b/internal/provider/notification_telegram_resource.go @@ -0,0 +1,358 @@ +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 ( + notificationTelegramResourceName = "notification_telegram" + NotificationTelegramImplementation = "Telegram" + NotificationTelegramConfigContrat = "TelegramSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationTelegramResource{} +var _ resource.ResourceWithImportState = &NotificationTelegramResource{} + +func NewNotificationTelegramResource() resource.Resource { + return &NotificationTelegramResource{} +} + +// NotificationTelegramResource defines the notification implementation. +type NotificationTelegramResource struct { + client *radarr.Radarr +} + +// NotificationTelegram describes the notification data model. +type NotificationTelegram struct { + Tags types.Set `tfsdk:"tags"` + ChatID types.String `tfsdk:"chat_id"` + Name types.String `tfsdk:"name"` + BotToken types.String `tfsdk:"bot_token"` + ID types.Int64 `tfsdk:"id"` + SendSilently types.Bool `tfsdk:"send_silently"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationTelegram) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + ChatID: n.ChatID, + BotToken: n.BotToken, + SendSilently: n.SendSilently, + Name: n.Name, + ID: n.ID, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationTelegram) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.ChatID = notification.ChatID + n.BotToken = notification.BotToken + n.SendSilently = notification.SendSilently + n.Name = notification.Name + n.ID = notification.ID + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationTelegramResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationTelegramResourceName +} + +func (r *NotificationTelegramResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Telegram resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Telegram](https://wiki.servarr.com/radarr/supported#telegram).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationTelegram 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 + "send_silently": schema.BoolAttribute{ + MarkdownDescription: "Send silently flag.", + Optional: true, + Computed: true, + }, + "chat_id": schema.StringAttribute{ + MarkdownDescription: "Chat ID.", + Required: true, + }, + "bot_token": schema.StringAttribute{ + MarkdownDescription: "Bot token.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *NotificationTelegramResource) 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 *NotificationTelegramResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationTelegram + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationTelegram + 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", notificationTelegramResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationTelegramResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTelegramResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationTelegram + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationTelegram 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", notificationTelegramResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationTelegramResourceName+": "+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 *NotificationTelegramResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationTelegram + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationTelegram + 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", notificationTelegramResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationTelegramResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTelegramResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationTelegram + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationTelegram 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", notificationTelegramResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationTelegramResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationTelegramResource) 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 "+notificationTelegramResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationTelegram) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationTelegram) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationTelegramConfigContrat, + Implementation: NotificationTelegramImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_telegram_resource_test.go b/internal/provider/notification_telegram_resource_test.go new file mode 100644 index 00000000..a0f598d2 --- /dev/null +++ b/internal/provider/notification_telegram_resource_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationTelegramResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationTelegramResourceConfig("resourceTelegramTest", "chat01"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_telegram.test", "chat_id", "chat01"), + resource.TestCheckResourceAttrSet("radarr_notification_telegram.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationTelegramResourceConfig("resourceTelegramTest", "chat02"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_telegram.test", "chat_id", "chat02"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_telegram.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationTelegramResourceConfig(name, chat string) string { + return fmt.Sprintf(` + resource "radarr_notification_telegram" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + chat_id = "%s" + bot_token = "Token" + }`, name, chat) +} diff --git a/internal/provider/notification_trakt_resource.go b/internal/provider/notification_trakt_resource.go new file mode 100644 index 00000000..3b72e2fe --- /dev/null +++ b/internal/provider/notification_trakt_resource.go @@ -0,0 +1,342 @@ +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 ( + notificationTraktResourceName = "notification_trakt" + NotificationTraktImplementation = "Trakt" + NotificationTraktConfigContrat = "TraktSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationTraktResource{} +var _ resource.ResourceWithImportState = &NotificationTraktResource{} + +func NewNotificationTraktResource() resource.Resource { + return &NotificationTraktResource{} +} + +// NotificationTraktResource defines the notification implementation. +type NotificationTraktResource struct { + client *radarr.Radarr +} + +// NotificationTrakt describes the notification data model. +type NotificationTrakt struct { + Tags types.Set `tfsdk:"tags"` + AuthUser types.String `tfsdk:"auth_user"` + AccessToken types.String `tfsdk:"access_token"` + RefreshToken types.String `tfsdk:"refresh_token"` + Expires types.String `tfsdk:"expires"` + Name types.String `tfsdk:"name"` + ID types.Int64 `tfsdk:"id"` + 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"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationTrakt) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + AuthUser: n.AuthUser, + Name: n.Name, + AccessToken: n.AccessToken, + RefreshToken: n.RefreshToken, + ID: n.ID, + Expires: n.Expires, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationTrakt) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.AuthUser = notification.AuthUser + n.Name = notification.Name + n.AccessToken = notification.AccessToken + n.RefreshToken = notification.RefreshToken + n.Expires = notification.Expires + n.ID = notification.ID + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationTraktResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationTraktResourceName +} + +func (r *NotificationTraktResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Trakt resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Trakt](https://wiki.servarr.com/radarr/supported#trakt).", + 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_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: "NotificationTrakt 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 + "access_token": schema.StringAttribute{ + MarkdownDescription: "Access Token.", + Required: true, + Sensitive: true, + }, + "refresh_token": schema.StringAttribute{ + MarkdownDescription: "Access Token.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "auth_user": schema.StringAttribute{ + MarkdownDescription: "Auth user.", + Required: true, + }, + "expires": schema.StringAttribute{ + MarkdownDescription: "expires.", + Computed: true, + }, + }, + } +} + +func (r *NotificationTraktResource) 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 *NotificationTraktResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationTrakt + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationTrakt + 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", notificationTraktResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationTraktResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTraktResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationTrakt + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationTrakt 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", notificationTraktResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationTraktResourceName+": "+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 *NotificationTraktResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationTrakt + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationTrakt + 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", notificationTraktResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationTraktResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTraktResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationTrakt + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationTrakt 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", notificationTraktResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationTraktResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationTraktResource) 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 "+notificationTraktResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationTrakt) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationTrakt) 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(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationTraktConfigContrat, + Implementation: NotificationTraktImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_trakt_resource_test.go b/internal/provider/notification_trakt_resource_test.go new file mode 100644 index 00000000..90947cb3 --- /dev/null +++ b/internal/provider/notification_trakt_resource_test.go @@ -0,0 +1,59 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationTraktResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationTraktResourceConfig("resourceTraktTest", "token123"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_trakt.test", "access_token", "token123"), + resource.TestCheckResourceAttrSet("radarr_notification_trakt.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationTraktResourceConfig("resourceTraktTest", "token234"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_trakt.test", "access_token", "token234"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_trakt.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationTraktResourceConfig(name, token string) string { + return fmt.Sprintf(` + resource "radarr_notification_trakt" "test" { + on_download = false + on_upgrade = 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" + + auth_user = "User" + access_token = "%s" + }`, name, token) +} diff --git a/internal/provider/notification_twitter_resource.go b/internal/provider/notification_twitter_resource.go new file mode 100644 index 00000000..1c2a9014 --- /dev/null +++ b/internal/provider/notification_twitter_resource.go @@ -0,0 +1,382 @@ +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 ( + notificationTwitterResourceName = "notification_twitter" + NotificationTwitterImplementation = "Twitter" + NotificationTwitterConfigContrat = "TwitterSettings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &NotificationTwitterResource{} +var _ resource.ResourceWithImportState = &NotificationTwitterResource{} + +func NewNotificationTwitterResource() resource.Resource { + return &NotificationTwitterResource{} +} + +// NotificationTwitterResource defines the notification implementation. +type NotificationTwitterResource struct { + client *radarr.Radarr +} + +// NotificationTwitter describes the notification data model. +type NotificationTwitter struct { + Tags types.Set `tfsdk:"tags"` + Name types.String `tfsdk:"name"` + AccessToken types.String `tfsdk:"access_token"` + AccessTokenSecret types.String `tfsdk:"access_token_secret"` + ConsumerKey types.String `tfsdk:"consumer_key"` + ConsumerSecret types.String `tfsdk:"consumer_secret"` + Mention types.String `tfsdk:"mention"` + ID types.Int64 `tfsdk:"id"` + DirectMessage types.Bool `tfsdk:"direct_message"` + OnGrab types.Bool `tfsdk:"on_grab"` + 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"` + OnApplicationUpdate types.Bool `tfsdk:"on_application_update"` + OnHealthIssue types.Bool `tfsdk:"on_health_issue"` + OnMovieDelete types.Bool `tfsdk:"on_movie_delete"` + OnUpgrade types.Bool `tfsdk:"on_upgrade"` + OnDownload types.Bool `tfsdk:"on_download"` +} + +func (n NotificationTwitter) toNotification() *Notification { + return &Notification{ + Tags: n.Tags, + AccessToken: n.AccessToken, + AccessTokenSecret: n.AccessTokenSecret, + ConsumerKey: n.ConsumerKey, + ConsumerSecret: n.ConsumerSecret, + Mention: n.Mention, + Name: n.Name, + ID: n.ID, + DirectMessage: n.DirectMessage, + OnGrab: n.OnGrab, + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade, + OnMovieAdded: n.OnMovieAdded, + OnMovieFileDelete: n.OnMovieFileDelete, + IncludeHealthWarnings: n.IncludeHealthWarnings, + OnApplicationUpdate: n.OnApplicationUpdate, + OnHealthIssue: n.OnHealthIssue, + OnMovieDelete: n.OnMovieDelete, + OnUpgrade: n.OnUpgrade, + OnDownload: n.OnDownload, + } +} + +func (n *NotificationTwitter) fromNotification(notification *Notification) { + n.Tags = notification.Tags + n.AccessToken = notification.AccessToken + n.AccessTokenSecret = notification.AccessTokenSecret + n.ConsumerKey = notification.ConsumerKey + n.ConsumerSecret = notification.ConsumerSecret + n.Mention = notification.Mention + n.Name = notification.Name + n.ID = notification.ID + n.DirectMessage = notification.DirectMessage + n.OnGrab = notification.OnGrab + n.OnMovieFileDeleteForUpgrade = notification.OnMovieFileDeleteForUpgrade + n.OnMovieFileDelete = notification.OnMovieFileDelete + n.IncludeHealthWarnings = notification.IncludeHealthWarnings + n.OnApplicationUpdate = notification.OnApplicationUpdate + n.OnHealthIssue = notification.OnHealthIssue + n.OnMovieAdded = notification.OnMovieAdded + n.OnMovieDelete = notification.OnMovieDelete + n.OnUpgrade = notification.OnUpgrade + n.OnDownload = notification.OnDownload +} + +func (r *NotificationTwitterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + notificationTwitterResourceName +} + +func (r *NotificationTwitterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Notification Twitter resource.\nFor more information refer to [Notification](https://wiki.servarr.com/radarr/settings#connect) and [Twitter](https://wiki.servarr.com/radarr/supported#twitter).", + Attributes: map[string]schema.Attribute{ + "on_grab": schema.BoolAttribute{ + MarkdownDescription: "On grab flag.", + Required: true, + }, + "on_download": schema.BoolAttribute{ + MarkdownDescription: "On download flag.", + Required: true, + }, + "on_upgrade": schema.BoolAttribute{ + MarkdownDescription: "On upgrade 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, + }, + "on_health_issue": schema.BoolAttribute{ + MarkdownDescription: "On health issue flag.", + Required: true, + }, + "on_application_update": schema.BoolAttribute{ + MarkdownDescription: "On application update flag.", + Required: true, + }, + "include_health_warnings": schema.BoolAttribute{ + MarkdownDescription: "Include health warnings.", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "NotificationTwitter 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 + "direct_message": schema.BoolAttribute{ + MarkdownDescription: "Direct message flag.", + Optional: true, + Computed: true, + }, + "consumer_key": schema.StringAttribute{ + MarkdownDescription: "Consumer Key.", + Required: true, + Sensitive: true, + }, + "consumer_secret": schema.StringAttribute{ + MarkdownDescription: "Consumer Secret.", + Required: true, + Sensitive: true, + }, + "access_token": schema.StringAttribute{ + MarkdownDescription: "Access token.", + Required: true, + Sensitive: true, + }, + "access_token_secret": schema.StringAttribute{ + MarkdownDescription: "Access token secret.", + Required: true, + Sensitive: true, + }, + "mention": schema.StringAttribute{ + MarkdownDescription: "Mention.", + Required: true, + }, + }, + } +} + +func (r *NotificationTwitterResource) 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 *NotificationTwitterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var notification *NotificationTwitter + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new NotificationTwitter + 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", notificationTwitterResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+notificationTwitterResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTwitterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var notification *NotificationTwitter + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get NotificationTwitter 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", notificationTwitterResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+notificationTwitterResourceName+": "+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 *NotificationTwitterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var notification *NotificationTwitter + + resp.Diagnostics.Append(req.Plan.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update NotificationTwitter + 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", notificationTwitterResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+notificationTwitterResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + notification.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, ¬ification)...) +} + +func (r *NotificationTwitterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var notification *NotificationTwitter + + resp.Diagnostics.Append(req.State.Get(ctx, ¬ification)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete NotificationTwitter 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", notificationTwitterResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+notificationTwitterResourceName+": "+strconv.Itoa(int(notification.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *NotificationTwitterResource) 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 "+notificationTwitterResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (n *NotificationTwitter) write(ctx context.Context, notification *radarr.NotificationOutput) { + genericNotification := Notification{ + OnGrab: types.BoolValue(notification.OnGrab), + OnDownload: types.BoolValue(notification.OnDownload), + OnUpgrade: types.BoolValue(notification.OnUpgrade), + 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 *NotificationTwitter) read(ctx context.Context) *radarr.NotificationInput { + var tags []int + + tfsdk.ValueAs(ctx, n.Tags, &tags) + + return &radarr.NotificationInput{ + OnGrab: n.OnGrab.ValueBool(), + OnDownload: n.OnDownload.ValueBool(), + OnUpgrade: n.OnUpgrade.ValueBool(), + OnMovieAdded: n.OnMovieAdded.ValueBool(), + OnMovieDelete: n.OnMovieDelete.ValueBool(), + OnMovieFileDelete: n.OnMovieFileDelete.ValueBool(), + OnMovieFileDeleteForUpgrade: n.OnMovieFileDeleteForUpgrade.ValueBool(), + OnHealthIssue: n.OnHealthIssue.ValueBool(), + OnApplicationUpdate: n.OnApplicationUpdate.ValueBool(), + IncludeHealthWarnings: n.IncludeHealthWarnings.ValueBool(), + ConfigContract: NotificationTwitterConfigContrat, + Implementation: NotificationTwitterImplementation, + ID: n.ID.ValueInt64(), + Name: n.Name.ValueString(), + Tags: tags, + Fields: n.toNotification().readFields(ctx), + } +} diff --git a/internal/provider/notification_twitter_resource_test.go b/internal/provider/notification_twitter_resource_test.go new file mode 100644 index 00000000..fd86d72a --- /dev/null +++ b/internal/provider/notification_twitter_resource_test.go @@ -0,0 +1,65 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNotificationTwitterResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccNotificationTwitterResourceConfig("resourceTwitterTest", "me"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_twitter.test", "mention", "me"), + resource.TestCheckResourceAttrSet("radarr_notification_twitter.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccNotificationTwitterResourceConfig("resourceTwitterTest", "myself"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_notification_twitter.test", "mention", "myself"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_notification_twitter.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNotificationTwitterResourceConfig(name, mention string) string { + return fmt.Sprintf(` + resource "radarr_notification_twitter" "test" { + on_grab = false + on_download = false + on_upgrade = false + on_movie_added = false + on_movie_delete = false + on_movie_file_delete = false + on_movie_file_delete_for_upgrade = false + on_health_issue = false + on_application_update = false + + include_health_warnings = false + name = "%s" + + access_token = "Token" + access_token_secret = "TokenSecret" + consumer_key = "Key" + consumer_secret = "Secret" + mention = "%s" + }`, name, mention) +} diff --git a/internal/provider/notifications_data_source.go b/internal/provider/notifications_data_source.go index 71aab81e..39789883 100644 --- a/internal/provider/notifications_data_source.go +++ b/internal/provider/notifications_data_source.go @@ -154,16 +154,12 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch MarkdownDescription: "Use SSL flag.", Computed: true, }, - "port": schema.Int64Attribute{ - MarkdownDescription: "Port.", - Computed: true, - }, - "grab_fields": schema.Int64Attribute{ - MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore.", + "display_time": schema.Int64Attribute{ + MarkdownDescription: "Display time.", Computed: true, }, - "import_fields": schema.Int64Attribute{ - MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", Computed: true, }, "method": schema.Int64Attribute{ @@ -222,18 +218,10 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch MarkdownDescription: "Instance name.", Computed: true, }, - "bcc": schema.StringAttribute{ - MarkdownDescription: "Bcc.", - Computed: true, - }, "bot_token": schema.StringAttribute{ MarkdownDescription: "Bot token.", Computed: true, }, - "cc": schema.StringAttribute{ - MarkdownDescription: "Cc.", - Computed: true, - }, "channel": schema.StringAttribute{ MarkdownDescription: "Channel.", Computed: true, @@ -254,10 +242,6 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch MarkdownDescription: "Device names.", Computed: true, }, - "display_time": schema.StringAttribute{ - MarkdownDescription: "Display time.", - Computed: true, - }, "expires": schema.StringAttribute{ MarkdownDescription: "Expires.", Computed: true, @@ -310,10 +294,6 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch MarkdownDescription: "Sound.", Computed: true, }, - "to": schema.StringAttribute{ - MarkdownDescription: "To.", - Computed: true, - }, "token": schema.StringAttribute{ MarkdownDescription: "Token.", Computed: true, @@ -361,7 +341,7 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch "device_ids": schema.SetAttribute{ MarkdownDescription: "Device IDs.", Computed: true, - ElementType: types.Int64Type, + ElementType: types.StringType, }, "channel_tags": schema.SetAttribute{ MarkdownDescription: "Channel tags.", @@ -374,12 +354,22 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch ElementType: types.StringType, }, "topics": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Topics.", Computed: true, ElementType: types.StringType, }, + "grab_fields": schema.SetAttribute{ + MarkdownDescription: "Grab fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Group, `5` Size, `6` Links, `7` Release, `8` Poster, `9` Fanart, `10` CustomFormats, `11` CustomFormatScore.", + Computed: true, + ElementType: types.Int64Type, + }, + "import_fields": schema.SetAttribute{ + MarkdownDescription: "Import fields. `0` Overview, `1` Rating, `2` Genres, `3` Quality, `4` Codecs, `5` Group, `6` Size, `7` Languages, `8` Subtitles, `9` Links, `10` Release, `11` Poster, `12` Fanart.", + Computed: true, + ElementType: types.Int64Type, + }, "field_tags": schema.SetAttribute{ - MarkdownDescription: "Devices.", + MarkdownDescription: "Specific tags.", Computed: true, ElementType: types.StringType, }, @@ -388,6 +378,21 @@ func (d *NotificationsDataSource) Schema(ctx context.Context, req datasource.Sch Computed: true, ElementType: types.StringType, }, + "to": schema.SetAttribute{ + MarkdownDescription: "To.", + Computed: true, + ElementType: types.StringType, + }, + "cc": schema.SetAttribute{ + MarkdownDescription: "Cc.", + Computed: true, + ElementType: types.StringType, + }, + "bcc": schema.SetAttribute{ + MarkdownDescription: "Bcc.", + Computed: true, + ElementType: types.StringType, + }, }, }, }, diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3d037fa9..44889009 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -174,7 +174,28 @@ func (p *RadarrProvider) Resources(ctx context.Context) []func() resource.Resour // Notifications NewNotificationResource, + NewNotificationBoxcarResource, NewNotificationCustomScriptResource, + NewNotificationDiscordResource, + NewNotificationEmailResource, + NewNotificationEmbyResource, + NewNotificationGotifyResource, + NewNotificationJoinResource, + NewNotificationKodiResource, + NewNotificationMailgunResource, + NewNotificationNotifiarrResource, + NewNotificationNtfyResource, + NewNotificationPlexResource, + NewNotificationProwlResource, + NewNotificationPushbulletResource, + NewNotificationPushoverResource, + NewNotificationSendgridResource, + NewNotificationSimplepushResource, + NewNotificationSlackResource, + NewNotificationSynologyResource, + NewNotificationTelegramResource, + NewNotificationTraktResource, + NewNotificationTwitterResource, NewNotificationWebhookResource, // Profiles