From fba8af756f2e380df4e225b5104384df0f10aa6a Mon Sep 17 00:00:00 2001 From: Fuochi Date: Thu, 26 Jan 2023 14:20:08 +0400 Subject: [PATCH] feat: add import list trakt user resource --- docs/resources/import_list_trakt_popular.md | 2 +- docs/resources/import_list_trakt_user.md | 72 ++++ .../radarr_import_list_trakt_user/import.sh | 2 + .../radarr_import_list_trakt_user/resource.tf | 14 + .../import_list_trakt_user_resource.go | 375 ++++++++++++++++++ .../import_list_trakt_user_resource_test.go | 60 +++ internal/provider/provider.go | 1 + 7 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 docs/resources/import_list_trakt_user.md create mode 100644 examples/resources/radarr_import_list_trakt_user/import.sh create mode 100644 examples/resources/radarr_import_list_trakt_user/resource.tf create mode 100644 internal/provider/import_list_trakt_user_resource.go create mode 100644 internal/provider/import_list_trakt_user_resource_test.go diff --git a/docs/resources/import_list_trakt_popular.md b/docs/resources/import_list_trakt_popular.md index 6fbf502f..041e277f 100644 --- a/docs/resources/import_list_trakt_popular.md +++ b/docs/resources/import_list_trakt_popular.md @@ -58,7 +58,7 @@ resource "radarr_import_list_trakt_popular" "example" { - `search_on_add` (Boolean) Search on add flag. - `tags` (Set of Number) List of associated tags. - `trakt_additional_parameters` (String) Trakt additional parameters. -- `trakt_list_type` (Number) Trakt list type. +- `trakt_list_type` (Number) Trakt list type.`0` Trending, `1` Popular, `2` Anticipated, `3` BoxOffice, `4` TopWatchedByWeek, `5` TopWatchedByMonth, `6` TopWatchedByYear, `7` TopWatchedByAllTime, `8` RecommendedByWeek, `9` RecommendedByMonth, `10` RecommendedByYear, `10` RecommendedByAllTime. - `years` (String) Years. ### Read-Only diff --git a/docs/resources/import_list_trakt_user.md b/docs/resources/import_list_trakt_user.md new file mode 100644 index 00000000..86d045dc --- /dev/null +++ b/docs/resources/import_list_trakt_user.md @@ -0,0 +1,72 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "radarr_import_list_trakt_user Resource - terraform-provider-radarr" +subcategory: "Import Lists" +description: |- + Import List Trakt User resource. + For more information refer to Import List https://wiki.servarr.com/radarr/settings#import-lists and Trakt User https://wiki.servarr.com/radarr/supported#traktuserimport. +--- + +# radarr_import_list_trakt_user (Resource) + +Import List Trakt User resource. +For more information refer to [Import List](https://wiki.servarr.com/radarr/settings#import-lists) and [Trakt User](https://wiki.servarr.com/radarr/supported#traktuserimport). + +## Example Usage + +```terraform +resource "radarr_import_list_trakt_user" "example" { + enabled = true + enable_auto = false + search_on_add = false + root_folder_path = "/config" + monitor = "none" + minimum_availability = "tba" + quality_profile_id = 1 + name = "Example" + auth_user = "User1" + access_token = "Token" + trakt_list_type = 0 + limit = 100 +} +``` + + +## Schema + +### Required + +- `access_token` (String, Sensitive) Access token. +- `auth_user` (String) Auth user. +- `limit` (Number) limit. +- `minimum_availability` (String) Minimum availability. +- `monitor` (String) Should monitor. +- `name` (String) Import List name. +- `quality_profile_id` (Number) Quality profile ID. +- `root_folder_path` (String) Root folder path. + +### Optional + +- `enable_auto` (Boolean) Enable automatic add flag. +- `enabled` (Boolean) Enabled flag. +- `expires` (String) Expires. +- `list_order` (Number) List order. +- `refresh_token` (String, Sensitive) Refresh token. +- `search_on_add` (Boolean) Search on add flag. +- `tags` (Set of Number) List of associated tags. +- `trakt_additional_parameters` (String) Trakt additional parameters. +- `trakt_list_type` (Number) Trakt list type.`0` UserWatchList, `1` UserWatchedList, `2` UserCollectionList. +- `username` (String) Username. + +### Read-Only + +- `id` (Number) Import List ID. + +## Import + +Import is supported using the following syntax: + +```shell +# import using the API/UI ID +terraform import radarr_import_list_trakt_user.example 1 +``` diff --git a/examples/resources/radarr_import_list_trakt_user/import.sh b/examples/resources/radarr_import_list_trakt_user/import.sh new file mode 100644 index 00000000..23dae396 --- /dev/null +++ b/examples/resources/radarr_import_list_trakt_user/import.sh @@ -0,0 +1,2 @@ +# import using the API/UI ID +terraform import radarr_import_list_trakt_user.example 1 \ No newline at end of file diff --git a/examples/resources/radarr_import_list_trakt_user/resource.tf b/examples/resources/radarr_import_list_trakt_user/resource.tf new file mode 100644 index 00000000..c3cf0548 --- /dev/null +++ b/examples/resources/radarr_import_list_trakt_user/resource.tf @@ -0,0 +1,14 @@ +resource "radarr_import_list_trakt_user" "example" { + enabled = true + enable_auto = false + search_on_add = false + root_folder_path = "/config" + monitor = "none" + minimum_availability = "tba" + quality_profile_id = 1 + name = "Example" + auth_user = "User1" + access_token = "Token" + trakt_list_type = 0 + limit = 100 +} \ No newline at end of file diff --git a/internal/provider/import_list_trakt_user_resource.go b/internal/provider/import_list_trakt_user_resource.go new file mode 100644 index 00000000..1facf486 --- /dev/null +++ b/internal/provider/import_list_trakt_user_resource.go @@ -0,0 +1,375 @@ +package provider + +import ( + "context" + "strconv" + + "github.com/devopsarr/radarr-go/radarr" + "github.com/devopsarr/terraform-provider-radarr/internal/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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" +) + +const ( + importListTraktUserResourceName = "import_list_trakt_user" + importListTraktUserImplementation = "TraktUserImport" + importListTraktUserConfigContract = "TraktUserSettings" + importListTraktUserType = "trakt" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var ( + _ resource.Resource = &ImportListTraktUserResource{} + _ resource.ResourceWithImportState = &ImportListTraktUserResource{} +) + +func NewImportListTraktUserResource() resource.Resource { + return &ImportListTraktUserResource{} +} + +// ImportListTraktUserResource defines the import list implementation. +type ImportListTraktUserResource struct { + client *radarr.APIClient +} + +// ImportListTraktUser describes the import list data model. +type ImportListTraktUser struct { + Tags types.Set `tfsdk:"tags"` + Name types.String `tfsdk:"name"` + Monitor types.String `tfsdk:"monitor"` + MinimumAvailability types.String `tfsdk:"minimum_availability"` + RootFolderPath types.String `tfsdk:"root_folder_path"` + AuthUser types.String `tfsdk:"auth_user"` + TraktAdditionalParameters types.String `tfsdk:"trakt_additional_parameters"` + AccessToken types.String `tfsdk:"access_token"` + RefreshToken types.String `tfsdk:"refresh_token"` + Expires types.String `tfsdk:"expires"` + Username types.String `tfsdk:"username"` + TraktListType types.Int64 `tfsdk:"trakt_list_type"` + Limit types.Int64 `tfsdk:"limit"` + ListOrder types.Int64 `tfsdk:"list_order"` + ID types.Int64 `tfsdk:"id"` + QualityProfileID types.Int64 `tfsdk:"quality_profile_id"` + Enabled types.Bool `tfsdk:"enabled"` + EnableAuto types.Bool `tfsdk:"enable_auto"` + SearchOnAdd types.Bool `tfsdk:"search_on_add"` +} + +func (i ImportListTraktUser) toImportList() *ImportList { + return &ImportList{ + Tags: i.Tags, + Name: i.Name, + Monitor: i.Monitor, + MinimumAvailability: i.MinimumAvailability, + RootFolderPath: i.RootFolderPath, + ListOrder: i.ListOrder, + RefreshToken: i.RefreshToken, + AccessToken: i.AccessToken, + Expires: i.Expires, + AuthUser: i.AuthUser, + TraktAdditionalParameters: i.TraktAdditionalParameters, + Username: i.Username, + TraktListType: i.TraktListType, + Limit: i.Limit, + ID: i.ID, + QualityProfileID: i.QualityProfileID, + Enabled: i.Enabled, + EnableAuto: i.EnableAuto, + SearchOnAdd: i.SearchOnAdd, + } +} + +func (i *ImportListTraktUser) fromImportList(importList *ImportList) { + i.Tags = importList.Tags + i.Name = importList.Name + i.Monitor = importList.Monitor + i.MinimumAvailability = importList.MinimumAvailability + i.RootFolderPath = importList.RootFolderPath + i.RefreshToken = importList.RefreshToken + i.AccessToken = importList.AccessToken + i.Expires = importList.Expires + i.AuthUser = importList.AuthUser + i.TraktAdditionalParameters = importList.TraktAdditionalParameters + i.Username = importList.Username + i.TraktListType = importList.TraktListType + i.Limit = importList.Limit + i.ListOrder = importList.ListOrder + i.ID = importList.ID + i.QualityProfileID = importList.QualityProfileID + i.Enabled = importList.Enabled + i.EnableAuto = importList.EnableAuto + i.SearchOnAdd = importList.SearchOnAdd +} + +func (r *ImportListTraktUserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + importListTraktUserResourceName +} + +func (r *ImportListTraktUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Import List Trakt User resource.\nFor more information refer to [Import List](https://wiki.servarr.com/radarr/settings#import-lists) and [Trakt User](https://wiki.servarr.com/radarr/supported#traktuserimport).", + Attributes: map[string]schema.Attribute{ + "enable_auto": schema.BoolAttribute{ + MarkdownDescription: "Enable automatic add flag.", + Optional: true, + Computed: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enabled flag.", + Optional: true, + Computed: true, + }, + "search_on_add": schema.BoolAttribute{ + MarkdownDescription: "Search on add flag.", + Optional: true, + Computed: true, + }, + "quality_profile_id": schema.Int64Attribute{ + MarkdownDescription: "Quality profile ID.", + Required: true, + }, + "list_order": schema.Int64Attribute{ + MarkdownDescription: "List order.", + Optional: true, + Computed: true, + }, + "root_folder_path": schema.StringAttribute{ + MarkdownDescription: "Root folder path.", + Required: true, + }, + "monitor": schema.StringAttribute{ + MarkdownDescription: "Should monitor.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("movieOnly", "movieAndCollection", "none"), + }, + }, + "minimum_availability": schema.StringAttribute{ + MarkdownDescription: "Minimum availability.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("tba", "announced", "inCinemas", "released", "deleted"), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Import List name.", + Required: true, + }, + "tags": schema.SetAttribute{ + MarkdownDescription: "List of associated tags.", + Optional: true, + Computed: true, + ElementType: types.Int64Type, + }, + "id": schema.Int64Attribute{ + MarkdownDescription: "Import List ID.", + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + // Field values + "limit": schema.Int64Attribute{ + MarkdownDescription: "limit.", + Required: true, + }, + "trakt_list_type": schema.Int64Attribute{ + MarkdownDescription: "Trakt list type.`0` UserWatchList, `1` UserWatchedList, `2` UserCollectionList.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 1, 2), + }, + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username.", + Optional: true, + Computed: true, + }, + "auth_user": schema.StringAttribute{ + MarkdownDescription: "Auth user.", + Required: true, + }, + "access_token": schema.StringAttribute{ + MarkdownDescription: "Access token.", + Required: true, + Sensitive: true, + }, + "refresh_token": schema.StringAttribute{ + MarkdownDescription: "Refresh token.", + Optional: true, + Computed: true, + Sensitive: true, + }, + "expires": schema.StringAttribute{ + MarkdownDescription: "Expires.", + Optional: true, + Computed: true, + }, + "trakt_additional_parameters": schema.StringAttribute{ + MarkdownDescription: "Trakt additional parameters.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (r *ImportListTraktUserResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if client := helpers.ResourceConfigure(ctx, req, resp); client != nil { + r.client = client + } +} + +func (r *ImportListTraktUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var importList *ImportListTraktUser + + resp.Diagnostics.Append(req.Plan.Get(ctx, &importList)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create new ImportListTraktUser + request := importList.read(ctx) + + response, _, err := r.client.ImportListApi.CreateImportList(ctx).ImportListResource(*request).Execute() + if err != nil { + resp.Diagnostics.AddError(helpers.ClientError, helpers.ParseClientError(helpers.Create, importListTraktUserResourceName, err)) + + return + } + + tflog.Trace(ctx, "created "+importListTraktUserResourceName+": "+strconv.Itoa(int(response.GetId()))) + // Generate resource state struct + importList.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &importList)...) +} + +func (r *ImportListTraktUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var importList *ImportListTraktUser + + resp.Diagnostics.Append(req.State.Get(ctx, &importList)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get ImportListTraktUser current value + response, _, err := r.client.ImportListApi.GetImportListById(ctx, int32(importList.ID.ValueInt64())).Execute() + if err != nil { + resp.Diagnostics.AddError(helpers.ClientError, helpers.ParseClientError(helpers.Read, importListTraktUserResourceName, err)) + + return + } + + tflog.Trace(ctx, "read "+importListTraktUserResourceName+": "+strconv.Itoa(int(response.GetId()))) + // Map response body to resource schema attribute + importList.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &importList)...) +} + +func (r *ImportListTraktUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get plan values + var importList *ImportListTraktUser + + resp.Diagnostics.Append(req.Plan.Get(ctx, &importList)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update ImportListTraktUser + request := importList.read(ctx) + + response, _, err := r.client.ImportListApi.UpdateImportList(ctx, strconv.Itoa(int(request.GetId()))).ImportListResource(*request).Execute() + if err != nil { + resp.Diagnostics.AddError(helpers.ClientError, helpers.ParseClientError(helpers.Update, importListTraktUserResourceName, err)) + + return + } + + tflog.Trace(ctx, "updated "+importListTraktUserResourceName+": "+strconv.Itoa(int(response.GetId()))) + // Generate resource state struct + importList.write(ctx, response) + resp.Diagnostics.Append(resp.State.Set(ctx, &importList)...) +} + +func (r *ImportListTraktUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var importList *ImportListTraktUser + + resp.Diagnostics.Append(req.State.Get(ctx, &importList)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete ImportListTraktUser current value + _, err := r.client.ImportListApi.DeleteImportList(ctx, int32(importList.ID.ValueInt64())).Execute() + if err != nil { + resp.Diagnostics.AddError(helpers.ClientError, helpers.ParseClientError(helpers.Read, importListTraktUserResourceName, err)) + + return + } + + tflog.Trace(ctx, "deleted "+importListTraktUserResourceName+": "+strconv.Itoa(int(importList.ID.ValueInt64()))) + resp.State.RemoveResource(ctx) +} + +func (r *ImportListTraktUserResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + helpers.ImportStatePassthroughIntID(ctx, path.Root("id"), req, resp) + tflog.Trace(ctx, "imported "+importListTraktUserResourceName+": "+req.ID) +} + +func (i *ImportListTraktUser) write(ctx context.Context, importList *radarr.ImportListResource) { + genericImportList := ImportList{ + Name: types.StringValue(importList.GetName()), + Monitor: types.StringValue(string(importList.GetMonitor())), + MinimumAvailability: types.StringValue(string(importList.GetMinimumAvailability())), + RootFolderPath: types.StringValue(importList.GetRootFolderPath()), + ListOrder: types.Int64Value(int64(importList.GetListOrder())), + ID: types.Int64Value(int64(importList.GetId())), + QualityProfileID: types.Int64Value(int64(importList.GetQualityProfileId())), + Enabled: types.BoolValue(importList.GetEnabled()), + EnableAuto: types.BoolValue(importList.GetEnableAuto()), + SearchOnAdd: types.BoolValue(importList.GetSearchOnAdd()), + } + genericImportList.Tags, _ = types.SetValueFrom(ctx, types.Int64Type, importList.Tags) + genericImportList.writeFields(ctx, importList.Fields) + i.fromImportList(&genericImportList) +} + +func (i *ImportListTraktUser) read(ctx context.Context) *radarr.ImportListResource { + tags := make([]*int32, len(i.Tags.Elements())) + tfsdk.ValueAs(ctx, i.Tags, &tags) + + list := radarr.NewImportListResource() + list.SetMonitor(radarr.MonitorTypes(i.Monitor.ValueString())) + list.SetMinimumAvailability(radarr.MovieStatusType(i.MinimumAvailability.ValueString())) + list.SetRootFolderPath(i.RootFolderPath.ValueString()) + list.SetQualityProfileId(int32(i.QualityProfileID.ValueInt64())) + list.SetListOrder(int32(i.ListOrder.ValueInt64())) + list.SetEnableAuto(i.EnableAuto.ValueBool()) + list.SetEnabled(i.Enabled.ValueBool()) + list.SetSearchOnAdd(i.SearchOnAdd.ValueBool()) + list.SetListType(importListTraktUserType) + list.SetConfigContract(importListTraktUserConfigContract) + list.SetImplementation(importListTraktUserImplementation) + list.SetId(int32(i.ID.ValueInt64())) + list.SetName(i.Name.ValueString()) + list.SetTags(tags) + list.SetFields(i.toImportList().readFields(ctx)) + + return list +} diff --git a/internal/provider/import_list_trakt_user_resource_test.go b/internal/provider/import_list_trakt_user_resource_test.go new file mode 100644 index 00000000..5ec69d2f --- /dev/null +++ b/internal/provider/import_list_trakt_user_resource_test.go @@ -0,0 +1,60 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccImportListTraktUserResource(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + PreConfig: rootFolderDSInit, + Config: testAccImportListTraktUserResourceConfig("resourceTraktUserTest", "none"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_import_list_trakt_user.test", "monitor", "none"), + resource.TestCheckResourceAttrSet("radarr_import_list_trakt_user.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccImportListTraktUserResourceConfig("resourceTraktUserTest", "movieOnly"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("radarr_import_list_trakt_user.test", "monitor", "movieOnly"), + ), + }, + // ImportState testing + { + ResourceName: "radarr_import_list_trakt_user.test", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccImportListTraktUserResourceConfig(name, monitor string) string { + return fmt.Sprintf(` + resource "radarr_import_list_trakt_user" "test" { + enabled = false + enable_auto = false + search_on_add = false + root_folder_path = "/config" + monitor = "%s" + minimum_availability = "tba" + quality_profile_id = 1 + name = "%s" + auth_user = "User1" + access_token = "Token" + trakt_list_type = 0 + limit = 100 + }`, monitor, name) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1d8298df..be136b9c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -187,6 +187,7 @@ func (p *RadarrProvider) Resources(ctx context.Context) []func() resource.Resour NewImportListTMDBUserResource, NewImportListTraktListResource, NewImportListTraktPopularResource, + NewImportListTraktUserResource, NewImportListConfigResource, NewImportListExclusionResource,