Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/codeql-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ jobs:
# ---- Frontend Lint (reusable) ----
frontend-lint:
name: Frontend Lint
uses: chodeus/chodeus-ops/.github/workflows/node-ci.yml@aae195c19ec7069d2e91aca88107220ce807ef7e # main
uses: chodeus/chodeus-ops/.github/workflows/node-ci.yml@579feb04e3219248963bd27843298a8c2ea6b2fd # main
with:
node_version: '24'
package_json_dir: frontend
Expand All @@ -197,7 +197,7 @@ jobs:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: 'npm'
Expand Down Expand Up @@ -433,7 +433,7 @@ jobs:
notify-failure:
needs: [codeql-python, codeql-javascript, backend-lint, backend-smoke, frontend-lint, frontend-tests, branch-isolation-guard, develop-invariant-guard, docker-push]
if: failure() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
uses: chodeus/chodeus-ops/.github/workflows/notify-discord.yml@aae195c19ec7069d2e91aca88107220ce807ef7e # main
uses: chodeus/chodeus-ops/.github/workflows/notify-discord.yml@579feb04e3219248963bd27843298a8c2ea6b2fd # main
with:
event_type: build
status: failure
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dep-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ permissions: {}

jobs:
osv-scan:
uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@8deb546fdb875b9996d27d4950be7312dac076a1 # v2.5.0
permissions:
# Least-privilege for the reusable workflow: SARIF upload + checkout.
actions: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:

jobs:
release:
uses: chodeus/chodeus-ops/.github/workflows/release-please.yml@aae195c19ec7069d2e91aca88107220ce807ef7e # main
uses: chodeus/chodeus-ops/.github/workflows/release-please.yml@579feb04e3219248963bd27843298a8c2ea6b2fd # main

docker-version:
needs: release
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/repo-events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ permissions:

jobs:
notify:
uses: chodeus/chodeus-ops/.github/workflows/repo-events.yml@aae195c19ec7069d2e91aca88107220ce807ef7e # main
uses: chodeus/chodeus-ops/.github/workflows/repo-events.yml@579feb04e3219248963bd27843298a8c2ea6b2fd # main
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ permissions:

jobs:
stale:
uses: chodeus/chodeus-ops/.github/workflows/stale-issues.yml@aae195c19ec7069d2e91aca88107220ce807ef7e # main
uses: chodeus/chodeus-ops/.github/workflows/stale-issues.yml@579feb04e3219248963bd27843298a8c2ea6b2fd # main
2 changes: 1 addition & 1 deletion backend/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def setup_auth(
except ConfigError as e:
logger.error(f"Auth setup failed: {e}")
return error(
f"Failed to save auth configuration: {e}",
"Failed to save auth configuration",
code="AUTH_SETUP_ERROR",
status_code=500,
)
Expand Down
3 changes: 2 additions & 1 deletion backend/api/border_replacerr.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,9 @@ def generate_preview(
except (ValueError, KeyError, AttributeError) as e:
# A malformed holiday/border config must yield a clean 4xx, not an
# unhandled 500 on the preview endpoint.
logger.error(f"Invalid border/holiday configuration: {e}")
return error(
f"Invalid border/holiday configuration: {e}",
"Invalid border/holiday configuration",
code="BORDER_CONFIG_INVALID",
status_code=400,
)
Expand Down
12 changes: 6 additions & 6 deletions backend/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ async def get_config(
except ConfigError as e:
logger.error(f"Configuration error: {e}")
return error(
f"Configuration error: {str(e)}",
"Configuration error",
"CONFIG_ERROR",
status_code=500,
)
except Exception as e:
logger.error(f"Error retrieving configuration: {e}")
return error(
f"Error retrieving configuration: {str(e)}",
"Error retrieving configuration",
"CONFIG_RETRIEVAL_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -232,21 +232,21 @@ async def update_config(
except ConfigError as e:
logger.error(f"Configuration error: {e}")
return error(
f"Configuration error: {str(e)}",
"Configuration error",
code="CONFIG_ERROR",
status_code=500,
)
except ValueError as e:
logger.error(f"Configuration validation failed: {e}")
return error(
f"Configuration validation failed: {str(e)}",
"Configuration validation failed",
"CONFIG_VALIDATION_ERROR",
status_code=400,
)
except Exception as e:
logger.error(f"Configuration update failed: {e}")
return error(
f"Configuration update failed: {str(e)}",
"Configuration update failed",
code="CONFIG_UPDATE_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -294,7 +294,7 @@ async def reveal_secret(
except ConfigError as e:
logger.error(f"Configuration error: {e}")
return error(
f"Configuration error: {str(e)}",
"Configuration error",
"CONFIG_ERROR",
status_code=500,
)
Expand Down
58 changes: 43 additions & 15 deletions backend/api/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from backend.util.config import (
REDACTED_PLACEHOLDER,
ChubConfig,
ConfigError,
InstanceDetail,
load_config,
redact_secrets,
Expand Down Expand Up @@ -507,10 +508,10 @@ def _probe(probe):
"response_time_ms": 0,
}
except Exception as exc:
logger.error(f"Health probe failed for '{name}': {exc}")
return name, {
"service": service,
"status": "error",
"error": str(exc),
}

if probes:
Expand Down Expand Up @@ -593,7 +594,7 @@ async def get_instances(
except Exception as e:
logger.error(f"Error retrieving instances: {e}")
return error(
f"Error retrieving instances: {str(e)}",
"Error retrieving instances",
code="INSTANCES_RETRIEVAL_ERROR",
status_code=500,
)
Expand All @@ -602,9 +603,13 @@ async def get_instances(
class _PlexFetchError(Exception):
"""Carries the API error code/status the libraries endpoints surface."""

def __init__(self, message: str, code: str, status_code: int) -> None:
def __init__(
self, message: str, code: str, status_code: int, detail: str = ""
) -> None:
"""Split the public message from the server-only `detail`."""
super().__init__(message)
self.message = message
self.message = message # public: returned to the client
self.detail = detail # server-only: never put in a response
self.code = code
self.status_code = status_code

Expand Down Expand Up @@ -637,13 +642,16 @@ def _fetch_plex_libraries(plex_data: Any) -> list:
res = requests.get(url, headers=headers, timeout=5, allow_redirects=False)
except requests.exceptions.RequestException as req_exc:
raise _PlexFetchError(
f"Failed to connect to Plex server: {req_exc}",
"Failed to connect to Plex server",
"PLEX_CONNECTION_FAILED",
502,
) from req_exc
if not res.ok:
raise _PlexFetchError(
f"Plex server error: {res.text}", "PLEX_SERVER_ERROR", res.status_code
"Plex server error",
"PLEX_SERVER_ERROR",
res.status_code,
detail=res.text[:500],
)

import defusedxml.ElementTree as ET
Expand Down Expand Up @@ -786,7 +794,13 @@ def get_plex_libraries(
try:
libraries = _fetch_plex_libraries(plex_data)
except _PlexFetchError as fe:
logger.error("Plex libraries fetch failed: %s", fe.message)
# exc_info carries the __cause__ the public message no longer does
logger.error(
"Plex libraries fetch failed: %s %s",
fe.message,
fe.detail,
exc_info=True,
)
return error(fe.message, code=fe.code, status_code=fe.status_code)

libraries = _annotate_enabled(libraries, plex_data.enabled_libraries)
Expand All @@ -798,7 +812,7 @@ def get_plex_libraries(
except Exception as e:
logger.error(f"Unexpected error retrieving Plex libraries: {e}")
return error(
f"Error retrieving Plex libraries: {str(e)}",
"Error retrieving Plex libraries",
code="PLEX_LIBRARIES_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -1041,10 +1055,12 @@ def test_instance(
code="CONNECTION_FAILED",
status_code=502,
)
except ConfigError:
raise
except Exception as e:
logger.error(f"Connection test failed for {data.name} ({data.url}): {e}")
return error(
f"Connection test error: {str(e)}",
"Connection test error",
code="CONNECTION_TEST_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -1148,10 +1164,12 @@ async def create_instance(
{"service": service, "name": name},
)

except ConfigError:
raise
except Exception as e:
logger.error(f"Failed to create instance {data.name}: {e}")
return error(
f"Failed to create instance: {str(e)}",
"Failed to create instance",
code="INSTANCE_CREATE_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -1311,10 +1329,12 @@ async def update_instance(
{"service": service, "name": new_name},
)

except ConfigError:
raise
except Exception as e:
logger.error(f"Failed to update instance {instance_id}: {e}")
return error(
f"Failed to update instance: {str(e)}",
"Failed to update instance",
code="INSTANCE_UPDATE_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -1398,10 +1418,12 @@ async def delete_instance(
{"name": instance_id},
)

except ConfigError:
raise
except Exception as e:
logger.error(f"Failed to delete instance {instance_id}: {e}")
return error(
f"Failed to delete instance: {str(e)}",
"Failed to delete instance",
code="INSTANCE_DELETE_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -1755,6 +1777,8 @@ async def refresh_instance(
job_id = result.get("data", {}).get("job_id")
return ok(f"Refresh initiated for '{instance_id}'", {"job_id": job_id})
return error("Error enqueuing refresh", code="REFRESH_ERROR", status_code=500)
except ConfigError:
raise
except Exception as e:
logger.error(f"Error refreshing instance {instance_id}: {e}")
return error(
Expand Down Expand Up @@ -1814,6 +1838,8 @@ async def sync_instance(
job_id = result.get("data", {}).get("job_id")
return ok(f"Sync initiated for '{instance_id}'", {"job_id": job_id})
return error("Error enqueuing sync", code="SYNC_ERROR", status_code=500)
except ConfigError:
raise
except Exception as e:
logger.error(f"Error syncing instance {instance_id}: {e}")
return error(
Expand Down Expand Up @@ -1909,10 +1935,12 @@ async def toggle_instance(
},
)

except ConfigError:
raise
except Exception as e:
logger.error(f"Failed to toggle instance {instance_id}: {e}")
return error(
f"Failed to toggle instance: {str(e)}",
"Failed to toggle instance",
code="INSTANCE_TOGGLE_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -2008,7 +2036,7 @@ def get_instance_logs(
except Exception as e:
logger.error(f"Error retrieving logs for instance {instance_id}: {e}")
return error(
f"Error retrieving instance logs: {str(e)}",
"Error retrieving instance logs",
code="INSTANCE_LOGS_ERROR",
status_code=500,
)
Expand Down Expand Up @@ -2117,11 +2145,11 @@ def check_instance_health(
"response_time_ms": 0,
}
except Exception as exc:
logger.error(f"Health check failed for '{instance_id}': {exc}")
health_data = {
"name": instance_id,
"service": service,
"status": "error",
"error": str(exc),
}

return ok(f"Health check for '{instance_id}'", health_data)
Expand Down
Loading
Loading