diff --git a/docs/resources/download_client_utorrent.md b/docs/resources/download_client_utorrent.md new file mode 100644 index 00000000..a6d04b45 --- /dev/null +++ b/docs/resources/download_client_utorrent.md @@ -0,0 +1,67 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_download_client_utorrent Resource - terraform-provider-radarr" +subcategory: "Download Clients" +description: |- + Download Client uTorrent resource. + For more information refer to Download Client https://wiki.servarr.com/radarr/settings#download-clients and uTorrent https://wiki.servarr.com/radarr/supported#utorrent. +--- + +# radarr_download_client_utorrent (Resource) + +Download Client uTorrent resource. +For more information refer to [Download Client](https://wiki.servarr.com/radarr/settings#download-clients) and [uTorrent](https://wiki.servarr.com/radarr/supported#utorrent). + +## Example Usage + +```terraform +resource "radarr_download_client_utorrent" "example" { + enable = true + priority = 1 + name = "Example" + host = "utorrent" + url_base = "/utorrent/" + port = 9091 + movie_category = "tv-radarr" +} +``` + + +## Schema + +### Required + +- `name` (String) Download Client name. + +### Optional + +- `enable` (Boolean) Enable flag. +- `host` (String) host. +- `intial_state` (Number) Initial state, with Stop support. `0` Start, `1` ForceStart, `2` Pause, `3` Stop. +- `movie_category` (String) TV category. +- `movie_directory` (String) TV directory. +- `movie_imported_category` (String) TV imported category. +- `older_movie_priority` (Number) Older TV priority. `0` Last, `1` First. +- `password` (String, Sensitive) Password. +- `port` (Number) Port. +- `priority` (Number) Priority. +- `recent_movie_priority` (Number) Recent TV priority. `0` Last, `1` First. +- `remove_completed_downloads` (Boolean) Remove completed downloads flag. +- `remove_failed_downloads` (Boolean) Remove failed downloads flag. +- `tags` (Set of Number) List of associated tags. +- `url_base` (String) Base URL. +- `use_ssl` (Boolean) Use SSL flag. +- `username` (String) Username. + +### Read-Only + +- `id` (Number) Download Client ID. + +## Import + +Import is supported using the following syntax: + +```shell +# import using the API/UI ID +terraform import radarr_download_client_utorrent.example 1 +``` diff --git a/examples/resources/radarr_download_client_utorrent/import.sh b/examples/resources/radarr_download_client_utorrent/import.sh new file mode 100644 index 00000000..d8dfb241 --- /dev/null +++ b/examples/resources/radarr_download_client_utorrent/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_download_client_utorrent.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_download_client_utorrent/resource.tf b/examples/resources/radarr_download_client_utorrent/resource.tf new file mode 100644 index 00000000..2ce79465 --- /dev/null +++ b/examples/resources/radarr_download_client_utorrent/resource.tf @@ -0,0 +1,9 @@ +resource "radarr_download_client_utorrent" "example" { + enable = true + priority = 1 + name = "Example" + host = "utorrent" + url_base = "/utorrent/" + port = 9091 + movie_category = "tv-radarr" +} \ No newline at end of file diff --git a/internal/provider/download_client_utorrent_resource.go b/internal/provider/download_client_utorrent_resource.go new file mode 100644 index 00000000..e6e56ae1 --- /dev/null +++ b/internal/provider/download_client_utorrent_resource.go @@ -0,0 +1,395 @@ +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 ( + downloadClientUtorrentResourceName = "download_client_utorrent" + DownloadClientUtorrentImplementation = "UTorrent" + DownloadClientUtorrentConfigContrat = "UTorrentSettings" + DownloadClientUtorrentProtocol = "torrent" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &DownloadClientUtorrentResource{} +var _ resource.ResourceWithImportState = &DownloadClientUtorrentResource{} + +func NewDownloadClientUtorrentResource() resource.Resource { + return &DownloadClientUtorrentResource{} +} + +// DownloadClientUtorrentResource defines the download client implementation. +type DownloadClientUtorrentResource struct { + client *radarr.Radarr +} + +// DownloadClientUtorrent describes the download client data model. +type DownloadClientUtorrent struct { + Tags types.Set `tfsdk:"tags"` + MovieImportedCategory types.String `tfsdk:"movie_imported_category"` + Name types.String `tfsdk:"name"` + Host types.String `tfsdk:"host"` + URLBase types.String `tfsdk:"url_base"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + MovieCategory types.String `tfsdk:"movie_category"` + MovieDirectory types.String `tfsdk:"movie_directory"` + RecentMoviePriority types.Int64 `tfsdk:"recent_movie_priority"` + Priority types.Int64 `tfsdk:"priority"` + Port types.Int64 `tfsdk:"port"` + ID types.Int64 `tfsdk:"id"` + OlderMoviePriority types.Int64 `tfsdk:"older_movie_priority"` + IntialState types.Int64 `tfsdk:"intial_state"` + UseSsl types.Bool `tfsdk:"use_ssl"` + Enable types.Bool `tfsdk:"enable"` + RemoveFailedDownloads types.Bool `tfsdk:"remove_failed_downloads"` + RemoveCompletedDownloads types.Bool `tfsdk:"remove_completed_downloads"` +} + +func (d DownloadClientUtorrent) toDownloadClient() *DownloadClient { + return &DownloadClient{ + Tags: d.Tags, + Name: d.Name, + Host: d.Host, + URLBase: d.URLBase, + Username: d.Username, + Password: d.Password, + MovieCategory: d.MovieCategory, + MovieDirectory: d.MovieDirectory, + RecentMoviePriority: d.RecentMoviePriority, + OlderMoviePriority: d.OlderMoviePriority, + Priority: d.Priority, + Port: d.Port, + ID: d.ID, + MovieImportedCategory: d.MovieImportedCategory, + IntialState: d.IntialState, + UseSsl: d.UseSsl, + Enable: d.Enable, + RemoveFailedDownloads: d.RemoveFailedDownloads, + RemoveCompletedDownloads: d.RemoveCompletedDownloads, + } +} + +func (d *DownloadClientUtorrent) fromDownloadClient(client *DownloadClient) { + d.Tags = client.Tags + d.Name = client.Name + d.Host = client.Host + d.URLBase = client.URLBase + d.Username = client.Username + d.Password = client.Password + d.MovieCategory = client.MovieCategory + d.MovieDirectory = client.MovieDirectory + d.RecentMoviePriority = client.RecentMoviePriority + d.OlderMoviePriority = client.OlderMoviePriority + d.Priority = client.Priority + d.Port = client.Port + d.ID = client.ID + d.MovieImportedCategory = client.MovieImportedCategory + d.IntialState = client.IntialState + d.UseSsl = client.UseSsl + d.Enable = client.Enable + d.RemoveFailedDownloads = client.RemoveFailedDownloads + d.RemoveCompletedDownloads = client.RemoveCompletedDownloads +} + +func (r *DownloadClientUtorrentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + downloadClientUtorrentResourceName +} + +func (r *DownloadClientUtorrentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Download Client uTorrent resource.\nFor more information refer to [Download Client](https://wiki.servarr.com/radarr/settings#download-clients) and [uTorrent](https://wiki.servarr.com/radarr/supported#utorrent).", + Attributes: map[string]schema.Attribute{ + "enable": schema.BoolAttribute{ + MarkdownDescription: "Enable flag.", + Optional: true, + Computed: true, + }, + "remove_completed_downloads": schema.BoolAttribute{ + MarkdownDescription: "Remove completed downloads flag.", + Optional: true, + Computed: true, + }, + "remove_failed_downloads": schema.BoolAttribute{ + MarkdownDescription: "Remove failed downloads flag.", + Optional: true, + Computed: true, + }, + "priority": schema.Int64Attribute{ + MarkdownDescription: "Priority.", + Optional: true, + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Download Client name.", + Required: true, + }, + "tags": schema.SetAttribute{ + MarkdownDescription: "List of associated tags.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + "id": schema.Int64Attribute{ + MarkdownDescription: "Download Client ID.", + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + // Field values + "use_ssl": schema.BoolAttribute{ + MarkdownDescription: "Use SSL flag.", + Optional: true, + Computed: true, + }, + "port": schema.Int64Attribute{ + MarkdownDescription: "Port.", + Optional: true, + Computed: true, + }, + "recent_movie_priority": schema.Int64Attribute{ + MarkdownDescription: "Recent TV priority. `0` Last, `1` First.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 1), + }, + }, + "older_movie_priority": schema.Int64Attribute{ + MarkdownDescription: "Older TV priority. `0` Last, `1` First.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 1), + }, + }, + "intial_state": schema.Int64Attribute{ + MarkdownDescription: "Initial state, with Stop support. `0` Start, `1` ForceStart, `2` Pause, `3` Stop.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 1, 2, 3), + }, + }, + "host": schema.StringAttribute{ + MarkdownDescription: "host.", + Optional: true, + Computed: true, + }, + "url_base": schema.StringAttribute{ + MarkdownDescription: "Base 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, + }, + "movie_category": schema.StringAttribute{ + MarkdownDescription: "TV category.", + Optional: true, + Computed: true, + }, + "movie_imported_category": schema.StringAttribute{ + MarkdownDescription: "TV imported category.", + Optional: true, + Computed: true, + }, + "movie_directory": schema.StringAttribute{ + MarkdownDescription: "TV directory.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (r *DownloadClientUtorrentResource) 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 *DownloadClientUtorrentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var client *DownloadClientUtorrent + + resp.Diagnostics.Append(req.Plan.Get(ctx, &client)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new DownloadClientUtorrent + request := client.read(ctx) + + response, err := r.client.AddDownloadClientContext(ctx, request) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to create %s, got error: %s", downloadClientUtorrentResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+downloadClientUtorrentResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + client.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &client)...) +} + +func (r *DownloadClientUtorrentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var client DownloadClientUtorrent + + resp.Diagnostics.Append(req.State.Get(ctx, &client)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get DownloadClientUtorrent current value + response, err := r.client.GetDownloadClientContext(ctx, client.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to read %s, got error: %s", downloadClientUtorrentResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+downloadClientUtorrentResourceName+": "+strconv.Itoa(int(response.ID))) + // Map response body to resource schema attribute + client.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &client)...) +} + +func (r *DownloadClientUtorrentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var client *DownloadClientUtorrent + + resp.Diagnostics.Append(req.Plan.Get(ctx, &client)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update DownloadClientUtorrent + request := client.read(ctx) + + response, err := r.client.UpdateDownloadClientContext(ctx, request) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to update %s, got error: %s", downloadClientUtorrentResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+downloadClientUtorrentResourceName+": "+strconv.Itoa(int(response.ID))) + // Generate resource state struct + client.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &client)...) +} + +func (r *DownloadClientUtorrentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var client *DownloadClientUtorrent + + resp.Diagnostics.Append(req.State.Get(ctx, &client)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete DownloadClientUtorrent current value + err := r.client.DeleteDownloadClientContext(ctx, client.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError(tools.ClientError, fmt.Sprintf("Unable to read %s, got error: %s", downloadClientUtorrentResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+downloadClientUtorrentResourceName+": "+strconv.Itoa(int(client.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *DownloadClientUtorrentResource) 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 "+downloadClientUtorrentResourceName+": "+strconv.Itoa(id)) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} + +func (d *DownloadClientUtorrent) write(ctx context.Context, downloadClient *radarr.DownloadClientOutput) { + genericDownloadClient := DownloadClient{ + Enable: types.BoolValue(downloadClient.Enable), + RemoveCompletedDownloads: types.BoolValue(downloadClient.RemoveCompletedDownloads), + RemoveFailedDownloads: types.BoolValue(downloadClient.RemoveFailedDownloads), + Priority: types.Int64Value(int64(downloadClient.Priority)), + ID: types.Int64Value(downloadClient.ID), + Name: types.StringValue(downloadClient.Name), + } + genericDownloadClient.Tags, _ = types.SetValueFrom(ctx, types.Int64Type, downloadClient.Tags) + genericDownloadClient.writeFields(ctx, downloadClient.Fields) + d.fromDownloadClient(&genericDownloadClient) +} + +func (d *DownloadClientUtorrent) read(ctx context.Context) *radarr.DownloadClientInput { + var tags []int + + tfsdk.ValueAs(ctx, d.Tags, &tags) + + return &radarr.DownloadClientInput{ + Enable: d.Enable.ValueBool(), + RemoveCompletedDownloads: d.RemoveCompletedDownloads.ValueBool(), + RemoveFailedDownloads: d.RemoveFailedDownloads.ValueBool(), + Priority: int(d.Priority.ValueInt64()), + ID: d.ID.ValueInt64(), + ConfigContract: DownloadClientUtorrentConfigContrat, + Implementation: DownloadClientUtorrentImplementation, + Name: d.Name.ValueString(), + Protocol: DownloadClientUtorrentProtocol, + Tags: tags, + Fields: d.toDownloadClient().readFields(ctx), + } +} diff --git a/internal/provider/download_client_utorrent_resource_test.go b/internal/provider/download_client_utorrent_resource_test.go new file mode 100644 index 00000000..99fb83f3 --- /dev/null +++ b/internal/provider/download_client_utorrent_resource_test.go @@ -0,0 +1,55 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDownloadClientUtorrentResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccDownloadClientUtorrentResourceConfig("resourceUtorrentTest", "utorrent"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_download_client_utorrent.test", "host", "utorrent"), + resource.TestCheckResourceAttr("radarr_download_client_utorrent.test", "url_base", "/utorrent/"), + resource.TestCheckResourceAttrSet("radarr_download_client_utorrent.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccDownloadClientUtorrentResourceConfig("resourceUtorrentTest", "utorrent-host"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_download_client_utorrent.test", "host", "utorrent-host"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_download_client_utorrent.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccDownloadClientUtorrentResourceConfig(name, host string) string { + return fmt.Sprintf(` + resource "radarr_download_client_utorrent" "test" { + enable = false + priority = 1 + name = "%s" + host = "%s" + url_base = "/utorrent/" + port = 9091 + movie_category = "tv-radarr" + }`, name, host) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5d3712ce..4ab5af6c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -147,6 +147,7 @@ func (p *RadarrProvider) Resources(ctx context.Context) []func() resource.Resour NewDownloadClientTorrentDownloadStationResource, NewDownloadClientUsenetBlackholeResource, NewDownloadClientUsenetDownloadStationResource, + NewDownloadClientUtorrentResource, NewRemotePathMappingResource, // Indexers