flickr-immich-k8s-sync-operator 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- __version__ = "0.0.1"
1
+ __version__ = "0.0.2"
2
2
 
3
3
  import os
4
4
  import sys
@@ -17,6 +17,7 @@ class OperatorConfig:
17
17
  job_names: list[str]
18
18
  check_interval: int
19
19
  restart_delay: int
20
+ skip_delay_on_oom: bool
20
21
 
21
22
  @classmethod
22
23
  def from_env(cls) -> OperatorConfig:
@@ -32,6 +33,9 @@ class OperatorConfig:
32
33
  Seconds between check cycles (default ``60``).
33
34
  RESTART_DELAY : str, optional
34
35
  Seconds after failure before a Job is restarted (default ``3600``).
36
+ SKIP_DELAY_ON_OOM : str, optional
37
+ If ``"true"`` (case-insensitive), skip the restart delay when the
38
+ failure reason is ``OOMKilled`` (default ``"false"``).
35
39
 
36
40
  Raises
37
41
  ------
@@ -48,4 +52,5 @@ class OperatorConfig:
48
52
  job_names=job_names,
49
53
  check_interval=int(os.environ.get("CHECK_INTERVAL", "60")),
50
54
  restart_delay=int(os.environ.get("RESTART_DELAY", "3600")),
55
+ skip_delay_on_oom=os.environ.get("SKIP_DELAY_ON_OOM", "false").strip().lower() == "true",
51
56
  )
@@ -123,9 +123,16 @@ class JobRestartOperator:
123
123
  """Log pod details and restart the Job if the restart delay has elapsed."""
124
124
  elapsed = (datetime.now(timezone.utc) - failure_time).total_seconds()
125
125
 
126
- self._log_pod_details(job_name)
126
+ reasons = self._get_pod_failure_reasons(job_name)
127
+ skip_delay = self._cfg.skip_delay_on_oom and "OOMKilled" in reasons
127
128
 
128
- if elapsed >= self._cfg.restart_delay:
129
+ if skip_delay:
130
+ self._log.info(
131
+ "\t{} failed with OOMKilled — skipping restart delay, restarting immediately.",
132
+ job_name,
133
+ )
134
+ self._restart_job(job_name, shutdown_event)
135
+ elif elapsed >= self._cfg.restart_delay:
129
136
  self._log.info(
130
137
  "\t{} failed {:.0f}s ago (>= {}s). Deleting and recreating...",
131
138
  job_name,
@@ -142,8 +149,9 @@ class JobRestartOperator:
142
149
  remaining,
143
150
  )
144
151
 
145
- def _log_pod_details(self, job_name: str) -> None:
146
- """List pods for *job_name* and log exit codes + tail logs."""
152
+ def _get_pod_failure_reasons(self, job_name: str) -> set[str]:
153
+ """List pods for *job_name*, log exit codes + tail logs, and return termination reasons."""
154
+ reasons: set[str] = set()
147
155
  try:
148
156
  pods = self._core_v1.list_namespaced_pod(
149
157
  self._cfg.namespace,
@@ -157,7 +165,8 @@ class JobRestartOperator:
157
165
  if cs.state and cs.state.terminated:
158
166
  exit_code = cs.state.terminated.exit_code
159
167
  reason = cs.state.terminated.reason
160
- break
168
+ if reason:
169
+ reasons.add(reason)
161
170
  try:
162
171
  tail = self._core_v1.read_namespaced_pod_log(
163
172
  pod_name,
@@ -176,6 +185,7 @@ class JobRestartOperator:
176
185
  )
177
186
  except Exception:
178
187
  self._log.warning("\tCould not retrieve pod details for {}", job_name)
188
+ return reasons
179
189
 
180
190
  def _restart_job(self, job_name: str, shutdown_event: threading.Event) -> None:
181
191
  """Delete and recreate a Job from the cached manifest."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flickr-immich-k8s-sync-operator
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Operator for syncing photos from Flickr to Immich on Kubernetes
5
5
  Project-URL: Homepage, https://github.com/vroomfondel/flickr-immich-k8s-sync-operator
6
6
  Project-URL: Repository, https://github.com/vroomfondel/flickr-immich-k8s-sync-operator
@@ -26,12 +26,21 @@ Description-Content-Type: text/markdown
26
26
  [![mypy and pytests](https://github.com/vroomfondel/flickr-immich-k8s-sync-operator/actions/workflows/mypynpytests.yml/badge.svg)](https://github.com/vroomfondel/flickr-immich-k8s-sync-operator/actions/workflows/mypynpytests.yml)
27
27
  ![Cumulative Clones](https://img.shields.io/endpoint?logo=github&url=https://gist.githubusercontent.com/vroomfondel/ba86ae83a5d1cfffce03ce36d30fa02d/raw/flickr-immich-k8s-sync-operator_somestuff_clone_count.json)
28
28
  [![Docker Pulls](https://img.shields.io/docker/pulls/xomoxcc/flickr-immich-k8s-sync-operator?logo=docker)](https://hub.docker.com/r/xomoxcc/flickr-immich-k8s-sync-operator/tags)
29
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/flickr-immich-k8s-sync-operator?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=PyPi+Downloads)](https://pepy.tech/projects/flickr-immich-k8s-sync-operator)
30
+
31
+ [![Gemini_Generated_Image_gwjk7ggwjk7ggwjk_250x250.png](Gemini_Generated_Image_gwjk7ggwjk7ggwjk_250x250.png)](https://hub.docker.com/r/xomoxcc/flickr-immich-k8s-sync-operator)
29
32
 
30
33
  Kubernetes operator that watches per-user Flickr download Jobs in a namespace,
31
34
  restarts failed Jobs after a configurable delay, and retrieves pod logs and exit
32
35
  codes for failed Jobs before restarting them. Designed to run alongside
33
36
  [Immich](https://immich.app/) (self-hosted photo management).
34
37
 
38
+ ### Operator in Action
39
+
40
+ ![Operator log output](Bildschirmfoto_2026-02-02_18-43-40_blurred.png)
41
+
42
+ *The operator monitoring Flickr download Jobs in [k9s](https://k9scli.io/), detecting failures, and scheduling restarts.*
43
+
35
44
  ## Status
36
45
 
37
46
  **Beta (v0.0.1)** — the core job-restart loop is implemented and functional.
@@ -45,10 +54,19 @@ The project scaffolding (packaging, Docker image, CI) is in place.
45
54
  - On failure (after a configurable delay), **deletes** the Job with `Foreground` propagation policy and **recreates** it from a cached manifest
46
55
  - Logs pod exit codes and tail logs before every restart
47
56
 
57
+ ### How it works
58
+
59
+ 1. An Ansible playbook ([`kubectlstuff_flickr_downloader.yml`](https://github.com/vroomfondel/somestuff/blob/main/flickrdownloaderstuff/kubectlstuff_flickr_downloader.yml)) creates per-user Flickr download Jobs in the `flickr-downloader` namespace
60
+ 2. Each Job runs [`flickr_download`](https://github.com/beaufour/flickr-download) with `BACKOFF_EXIT_ON_429=true`, so it exits immediately on HTTP 429 rate-limit errors instead of sleeping
61
+ 3. Jobs mount host directories for config, backup, and cache per user
62
+ 4. This operator watches all configured Jobs for failure conditions
63
+ 5. When a Job fails, the operator logs pod exit codes and tail logs, waits `RESTART_DELAY` seconds (default 1 hour), then deletes and recreates the Job from a cached manifest
64
+ 6. The operator uses namespace-scoped RBAC with minimal permissions (Jobs, Pods, Pod logs)
65
+
48
66
  ## Prerequisites
49
67
 
50
68
  - A running Kubernetes cluster
51
- - Flickr download Jobs already deployed — the operator manages their lifecycle (restart on failure), not initial creation
69
+ - Per-user Flickr download Jobs already deployed (e.g. via the Ansible playbook above) — the operator manages their lifecycle (restart on failure), not initial creation
52
70
  - An [Immich](https://immich.app/) instance (for planned sync functionality)
53
71
 
54
72
  ## Configuration
@@ -60,6 +78,7 @@ The project scaffolding (packaging, Docker image, CI) is in place.
60
78
  | `JOB_NAMES` | Comma-separated Job names to monitor (**required**) | — |
61
79
  | `CHECK_INTERVAL` | Seconds between check cycles | `60` |
62
80
  | `RESTART_DELAY` | Seconds to wait after failure before restart | `3600` |
81
+ | `SKIP_DELAY_ON_OOM` | Skip restart delay when failure reason is `OOMKilled` | `false` |
63
82
 
64
83
  ## Kubernetes Deployment
65
84
 
@@ -126,7 +145,7 @@ spec:
126
145
  serviceAccountName: flickr-operator
127
146
  containers:
128
147
  - name: operator
129
- image: flickr-immich-k8s-sync-operator:latest
148
+ image: xomoxcc/flickr-immich-k8s-sync-operator:latest
130
149
  env:
131
150
  - name: JOB_NAMES
132
151
  value: "flickr-downloader-alice,flickr-downloader-bob"
@@ -138,6 +157,8 @@ spec:
138
157
  # value: "60" # default
139
158
  # - name: RESTART_DELAY
140
159
  # value: "3600" # default
160
+ # - name: SKIP_DELAY_ON_OOM
161
+ # value: "false" # default
141
162
  resources:
142
163
  requests:
143
164
  cpu: 50m
@@ -0,0 +1,10 @@
1
+ flickr_immich_k8s_sync_operator/__init__.py,sha256=HaemO7aY-iuhUX3tuNi7cE8lVs5jhi3uDvvNZd6NWQc,1086
2
+ flickr_immich_k8s_sync_operator/__main__.py,sha256=IYEbP3_Te17oGpMp2NCcMiUceWceKGKWXqk6ZelgOBA,1238
3
+ flickr_immich_k8s_sync_operator/config.py,sha256=8-0grb5goapBOU9EOIAvQX2mF8HnQbp3my834TYNXQc,2071
4
+ flickr_immich_k8s_sync_operator/operator.py,sha256=7JQEgGPEGTFgUq-dkum1IJjF0W8jUAIE-oFmSOiymwY,7962
5
+ flickr_immich_k8s_sync_operator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ flickr_immich_k8s_sync_operator-0.0.2.dist-info/METADATA,sha256=JEM3lx4oEHIYk22HbiOSe2nk7Ype7eN4qhjVqsJbEig,8705
7
+ flickr_immich_k8s_sync_operator-0.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
+ flickr_immich_k8s_sync_operator-0.0.2.dist-info/entry_points.txt,sha256=2ka6kPb3391I_-DmcFWZj7UyCsjJZ3OOQ2xxdRO8Rfs,98
9
+ flickr_immich_k8s_sync_operator-0.0.2.dist-info/licenses/LICENSE.md,sha256=RG51X65V_wNLuyG-RGcLXxFsKyZnlH5wNvK_5mMlOag,7560
10
+ flickr_immich_k8s_sync_operator-0.0.2.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- flickr_immich_k8s_sync_operator/__init__.py,sha256=5Agx1JJrCUbtY8GWdBcO_t9HZeDqsxLbGl4Kbs2P5cg,1086
2
- flickr_immich_k8s_sync_operator/__main__.py,sha256=IYEbP3_Te17oGpMp2NCcMiUceWceKGKWXqk6ZelgOBA,1238
3
- flickr_immich_k8s_sync_operator/config.py,sha256=K0h0Mdh3ikh0UPloEZ_zCrsIY51n04tCp69JP5DZjrw,1754
4
- flickr_immich_k8s_sync_operator/operator.py,sha256=mpi04TRa0slmf5FeCN9pX_hxvOTLkuYz7yBIL7At5K0,7470
5
- flickr_immich_k8s_sync_operator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- flickr_immich_k8s_sync_operator-0.0.1.dist-info/METADATA,sha256=2O77NBy0y9ayAFdB9lJT-4PzKLMx3FC1Nsr7t1dDuw4,6948
7
- flickr_immich_k8s_sync_operator-0.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
- flickr_immich_k8s_sync_operator-0.0.1.dist-info/entry_points.txt,sha256=2ka6kPb3391I_-DmcFWZj7UyCsjJZ3OOQ2xxdRO8Rfs,98
9
- flickr_immich_k8s_sync_operator-0.0.1.dist-info/licenses/LICENSE.md,sha256=RG51X65V_wNLuyG-RGcLXxFsKyZnlH5wNvK_5mMlOag,7560
10
- flickr_immich_k8s_sync_operator-0.0.1.dist-info/RECORD,,