Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/get current config #14

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

RynoM
Copy link

@RynoM RynoM commented Dec 10, 2022

Maybe too many changes to list, but most importantly:

  • support backup current configurations
  • added template that dictates (and shows) supported configurables
  • only update things that have changed
  • cfg file does not need to save all values of every setting, missing values will be retrieved when updating
  • add support for using environment variables in config file

@RynoM
Copy link
Author

RynoM commented Dec 10, 2022

@PierreMesure Very curious to hear what you think about it, I've spent quite a bit of time on it spread over the last months. I think it is in a state where it warrents a serious review. Let me know how you want to approach this/what I can do to help.

@tigattack
Copy link

I see in api.py there seems to be some provisions for accepting an API key rather than relying on access to initialize.js and, by extension, authentication being disabled. This would be a super useful feature and would resolve my problem in #13.

If I am wrong and it is fully implemented, how can this be used? I attempted to set a SONARR_API_KEY env var and sonarr.server.api_key in config.yml, neither of which seemed to work unfortunately.

That aside, this looks like some amazing work (albeit mostly beyond my understanding) and seems to fix a number of my gripes with Flemmarr. Thank you for your efforts!

@RynoM
Copy link
Author

RynoM commented Dec 16, 2022

Hi @tigattack you were on the right track indeed. In the beginning i'd added support to the api class to be able to take an api_key. Later on i'd forgotten about this and it wasn't passed through properly. With a small fix I just did, you should now be able to specify SONARR_API_KEY in the environment, just like you tried, but this time it should work :). Let me know how it goes!

@gottsman
Copy link

Excellent work @RynoM A couple comments/questions.

  1. I ran Flemmarr a couple times and apparently a config.yml was created. That file was eventually used to overwrite my existing install. I think it came from the default docker-compose.yml volume mount. I did include the api keys so I guess thats my fault.
  2. Perhaps an environment variable could be used to confirm environment overwrites.
  3. Can we append date/time to the end of the "config_backup.yml" file to allow for multiple versions? The files aren't very big. Perhaps we keep the last 'n' versions.
  4. After the exclusions were applied (deleted), Flemmarr errored out with the following message:
ERROR code 409 on: DELETE https://radarr.gottsman.com:443/api/v3/tag/1
{'message': "Tag with ID 1 'criterion' cannot be deleted since it's still in use", 'description': "NzbDrone.Core.Datastore.ModelConflictException: Tag with ID 1 'criterion' cannot be deleted since it's still in use\n   at NzbDrone.Core.Tags.TagService.Delete(Int32 tagId) in D:\\a\\1\\s\\src\\NzbDrone.Core\\Tags\\TagService.cs:line 169\n   at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass33_0.<WrapVoidMethod>b__0(Object target, Object[] parameters)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)\n   at Radarr.Http.Middleware.BufferingMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\BufferingMiddleware.cs:line 28\n   at Radarr.Http.Middleware.IfModifiedMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\IfModifiedMiddleware.cs:line 41\n   at Radarr.Http.Middleware.CacheHeaderMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\CacheHeaderMiddleware.cs:line 33\n   at Radarr.Http.Middleware.UrlBaseMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\UrlBaseMiddleware.cs:line 27\n   at Radarr.Http.Middleware.VersionMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\VersionMiddleware.cs:line 28\n   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)\n   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)\n   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)"}
Traceback (most recent call last):
  File "//run.py", line 26, in <module>
    main()
  File "//run.py", line 22, in main
    cfg.apply()
  File "/config.py", line 55, in apply
    check_and_apply(self)
  File "/config.py", line 52, in check_and_apply
    check_and_apply(item)
  File "/config.py", line 50, in check_and_apply
    item.apply()
  File "/models.py", line 67, in apply
    self.api.delete(self.resource, id=current['id'])
  File "/api.py", line 106, in delete
    self._raise_for_status_and_log(response)
  File "/api.py", line 53, in _raise_for_status_and_log
    response.raise_for_status()
  File "/usr/local/lib/python3.10/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 409 Client Error: Conflict for url: https://radarr.gottsman.com:443/api/v3/tag/1

@tigattack
Copy link

Thanks again, working great in most cases now!

One thing I've noticed is when applying a set of quality profiles, if they already exist I see the following in Sonarr's events (haven't tested other arr's yet):

Invalid request Validation failed: 
-- Items: Groups must contain multiple qualities
-- Items: Qualities can only be used once
-- Id: 'Id' must be greater than '0'.

Flemmarr's output shows:

Cannot update, likely a 'reserved' config item.
Updated: http://sonarr:8989/api/v3/qualityprofile/0
Cannot update, likely a 'reserved' config item.
Updated: http://sonarr:8989/api/v3/qualityprofile/0
Cannot update, likely a 'reserved' config item.
Updated: http://sonarr:8989/api/v3/qualityprofile/0

I imagine this is because the qualityprofile ID must be 0 when creating, but the 'true' qualityprofile ID must be used when updating it.

I'm not sure how easy this would be to work around, but I thought I'd mention it anyway.

@RynoM
Copy link
Author

RynoM commented Dec 19, 2022

Excellent work @RynoM A couple comments/questions.

  1. I ran Flemmarr a couple times and apparently a config.yml was created. That file was eventually used to overwrite my existing install. I think it came from the default docker-compose.yml volume mount. I did include the api keys so I guess thats my fault.
  2. Perhaps an environment variable could be used to confirm environment overwrites.
  3. Can we append date/time to the end of the "config_backup.yml" file to allow for multiple versions? The files aren't very big. Perhaps we keep the last 'n' versions.
  4. After the exclusions were applied (deleted), Flemmarr errored out with the following message:
ERROR code 409 on: DELETE https://radarr.gottsman.com:443/api/v3/tag/1
{'message': "Tag with ID 1 'criterion' cannot be deleted since it's still in use", 'description': "NzbDrone.Core.Datastore.ModelConflictException: Tag with ID 1 'criterion' cannot be deleted since it's still in use\n   at NzbDrone.Core.Tags.TagService.Delete(Int32 tagId) in D:\\a\\1\\s\\src\\NzbDrone.Core\\Tags\\TagService.cs:line 169\n   at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass33_0.<WrapVoidMethod>b__0(Object target, Object[] parameters)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)\n   at Radarr.Http.Middleware.BufferingMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\BufferingMiddleware.cs:line 28\n   at Radarr.Http.Middleware.IfModifiedMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\IfModifiedMiddleware.cs:line 41\n   at Radarr.Http.Middleware.CacheHeaderMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\CacheHeaderMiddleware.cs:line 33\n   at Radarr.Http.Middleware.UrlBaseMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\UrlBaseMiddleware.cs:line 27\n   at Radarr.Http.Middleware.VersionMiddleware.InvokeAsync(HttpContext context) in D:\\a\\1\\s\\src\\Radarr.Http\\Middleware\\VersionMiddleware.cs:line 28\n   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)\n   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)\n   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)"}
Traceback (most recent call last):
  File "//run.py", line 26, in <module>
    main()
  File "//run.py", line 22, in main
    cfg.apply()
  File "/config.py", line 55, in apply
    check_and_apply(self)
  File "/config.py", line 52, in check_and_apply
    check_and_apply(item)
  File "/config.py", line 50, in check_and_apply
    item.apply()
  File "/models.py", line 67, in apply
    self.api.delete(self.resource, id=current['id'])
  File "/api.py", line 106, in delete
    self._raise_for_status_and_log(response)
  File "/api.py", line 53, in _raise_for_status_and_log
    response.raise_for_status()
  File "/usr/local/lib/python3.10/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 409 Client Error: Conflict for url: https://radarr.gottsman.com:443/api/v3/tag/1

Thanks for the kind words!

  1. The config.yaml is the basic 'example' one that comes with the repo. I could have figured this would happen haha, so probably better to rename that one to example_config.yaml and such that we only apply something when users make a consious choice to.
  2. I think the action in point one would solve this?
  3. Smart idea! Preventing overwriting of backups seems very nice. I will try to include this. Probably without a 'last n', such that you can keep your whole history if you want to.
  4. This is because I made the choice to interpret the config as reflecting the full state of the system(s). So that when something is (implicitly) not configured in your config.yaml, Flemmarr will try to delete those items such that the config and the system are in the same state again. This error can then come from either 1: Bad config, you removed (or didnt add to config.yaml) a tag that is used in another resource (that is still in config.yaml). Meaning this error is valid, but should probably do a better job letting the user know its their fault. Or 2: The order of deletion is causing issues, it might be trying to delete an unconfigured tag before updating/deleting the resources using this tag. The second one is kind of hard to solve for the general case I think, as these relationships between resources are not really captured anywhere.

Also whether Flemmarr should actively delete unconfigured resources is an open question imo, it might be kind of unexpected/aggressive if not stated clearly beforehand.

@tigattack
Copy link

tigattack commented Dec 19, 2022

Smart idea! Preventing overwriting of backups seems very nice. I will try to include this. Probably without a 'last n', such that you can keep your whole history if you want to.

I think setting a default numbers of backups (last n) to retain is a good idea, but in this case it would probably be a good idea to add the option for the user to set 'last n' to be 0 to disable pruning of old versions, allowing them to retain the entire history.

Also whether Flemmarr should actively delete unconfigured resources is an open question imo, it might be kind of unexpected/aggressive if not stated clearly beforehand.

I personally think this feature is great and strongly believe it should be kept, but maybe it could be configurable via an environment variable for those who don't want it.

@RynoM
Copy link
Author

RynoM commented Dec 19, 2022

I imagine this is because the qualityprofile ID must be 0 when creating, but the 'true' qualityprofile ID must be used when updating it.

@tigattack This is indeed true and the reason I had to re-write a lot haha. But this should be solved by getting and comparing with the current config when applying a new one (and using the id's of the current config).

Also its kind of weird that its saying its updating qualityprofile/0 three times, I'd expect /0 /1 and /2 if there were three. Can you maybe share that part of your config? But good to hear it (mostly) works, let me know if you spot any other oddities.

@gottsman Just updated the name of the example config and added a timestamp to the backup file. The 'last n' is a nice-to-have maybe for later :)

@tigattack
Copy link

This is indeed true and the reason I had to re-write a lot haha.

I've been following the progress, this looked like a tricky one to crack for sure!

Also its kind of weird that its saying its updating qualityprofile/0 three times, I'd expect /0 /1 and /2 if there were three. Can you maybe share that part of your config?

Yeah, I thought the same...
Of course, I've chucked it into pastebin as it's almost 500 lines 😄 https://pastebin.com/T60QASxd

I should note that I've added more profiles and amended some of the existing ones since my original comment, but it's not changed anything in regards to the original issue and snippets I posted.

@RynoM
Copy link
Author

RynoM commented Dec 20, 2022

@tigattack It looks like the backup you posted was created using an older version, still including 'id' everywhere (these are now no longer added/needed for the config). And somehow there ended up being 5 with 'id: 0'. If you either make a new backup or remove the (nested) 'id' keys everywhere, I think it should work as expected.

@tigattack
Copy link

tigattack commented Dec 23, 2022

You're right, entirely my mistake, apologies. I built my config based on API requests and clearly got some bits wrong! I've amended my config to remove the IDs, and it matches the schema of the backup now.

However, I now get essentially the same error, albeit with the expected profile IDs:

2022-12-20T22:13:04.480452382Z Cannot update, likely a 'reserved' config item.
2022-12-20T22:13:04.480563821Z Updated: http://sonarr:8989/api/v3/qualityprofile/10
2022-12-20T22:13:04.579678797Z Cannot update, likely a 'reserved' config item.
2022-12-20T22:13:04.579728255Z Updated: http://sonarr:8989/api/v3/qualityprofile/11
2022-12-20T22:13:04.643764190Z Cannot update, likely a 'reserved' config item.
2022-12-20T22:13:04.643812658Z Updated: http://sonarr:8989/api/v3/qualityprofile/12
2022-12-20T22:13:04.702876530Z Cannot update, likely a 'reserved' config item.
2022-12-20T22:13:04.702973740Z Updated: http://sonarr:8989/api/v3/qualityprofile/13
2022-12-20T22:13:04.745305835Z Cannot update, likely a 'reserved' config item.
2022-12-20T22:13:04.745358842Z Updated: http://sonarr:8989/api/v3/qualityprofile/14

Diff'ing my config.yml and config_backup.yml shows some interesting results. It appears that the profile item are in a different order in the backup. If I copy/paste the qualityprofiles array from the backup to config.yml, it works correctly, but the profile items are then incorrectly ordered.

I've chucked the qualityprofile dicts from config.yml and config_backup.yml respectively into a pastebin: https://pastebin.com/Vmv5bF5V. The profiles from the backup start on L402.

Perhaps I'm misunderstanding how this works, or have made another mistake, but I'm a bit confused by this.

@RynoM
Copy link
Author

RynoM commented Dec 25, 2022

No worries :). The logs you're seeing now aren't necessarily errors. I put this statement in when I got statuscode 400 when trying to update something like Lidarr's metadataprofile 'None' (default one), to which Lidarr replied I couldn't because it was a 'reserved' item (so some things cant be updated). Maybe theres other instances when this happens too.

The ordering of the lists has changed because now before saving, the backup is sorted by 'id', such that when we retrieve the current settings and also sort them by 'id', there the biggest change that things line up such that we don't have to update much. But practically this is kind of an optimization and shouldn't matter much.

But its kind of weird that you're seeing this log for multiple qualityprofiles, if it was qualityprofile/0 i'd maybe understand. I will add an actual logger with different loglevels soon and add some debug statements here so that we can see what the actuall request and response looks like here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants