djaploy 1.2.7__tar.gz → 1.2.8__tar.gz

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.
Files changed (91) hide show
  1. {djaploy-1.2.7 → djaploy-1.2.8}/PKG-INFO +1 -1
  2. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/systemd/infra/djaploy_hooks.py +5 -5
  3. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/infra/djaploy_hooks.py +45 -11
  4. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/PKG-INFO +1 -1
  5. {djaploy-1.2.7 → djaploy-1.2.8}/pyproject.toml +1 -1
  6. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_bluegreen.py +1 -1
  7. {djaploy-1.2.7 → djaploy-1.2.8}/LICENSE +0 -0
  8. {djaploy-1.2.7 → djaploy-1.2.8}/README.md +0 -0
  9. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/__init__.py +0 -0
  10. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/app.py +0 -0
  11. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/__init__.py +0 -0
  12. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/borg/__init__.py +0 -0
  13. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/borg/infra/__init__.py +0 -0
  14. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/borg/infra/djaploy_hooks.py +0 -0
  15. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/janitor/__init__.py +0 -0
  16. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/janitor/apps.py +0 -0
  17. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/janitor/infra/__init__.py +0 -0
  18. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/janitor/infra/commands/createjanitoruser.py +0 -0
  19. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/janitor/infra/djaploy_hooks.py +0 -0
  20. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/nginx/__init__.py +0 -0
  21. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/nginx/apps.py +0 -0
  22. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/nginx/infra/__init__.py +0 -0
  23. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/nginx/infra/djaploy_hooks.py +0 -0
  24. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/rclone/__init__.py +0 -0
  25. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/rclone/apps.py +0 -0
  26. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/rclone/infra/__init__.py +0 -0
  27. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/rclone/infra/djaploy_hooks.py +0 -0
  28. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/sync_certs/__init__.py +0 -0
  29. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/sync_certs/apps.py +0 -0
  30. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/sync_certs/infra/__init__.py +0 -0
  31. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/sync_certs/infra/djaploy_hooks.py +0 -0
  32. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/systemd/__init__.py +0 -0
  33. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/systemd/apps.py +0 -0
  34. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/systemd/infra/__init__.py +0 -0
  35. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/tailscale/__init__.py +0 -0
  36. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/tailscale/apps.py +0 -0
  37. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/tailscale/infra/__init__.py +0 -0
  38. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/tailscale/infra/djaploy_hooks.py +0 -0
  39. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/versioning/__init__.py +0 -0
  40. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/versioning/apps.py +0 -0
  41. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/versioning/infra/__init__.py +0 -0
  42. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/apps/versioning/infra/djaploy_hooks.py +0 -0
  43. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/artifact.py +0 -0
  44. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/bin/__init__.py +0 -0
  45. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/bin/django_pyinfra.py +0 -0
  46. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/bin/gunicornherder.py +0 -0
  47. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/builtin_hooks.py +0 -0
  48. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/certificates.py +0 -0
  49. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/changelog.py +0 -0
  50. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/__init__.py +0 -0
  51. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/_utils.py +0 -0
  52. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/activate.py +0 -0
  53. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/configure.py +0 -0
  54. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/deploy.py +0 -0
  55. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/restore.py +0 -0
  56. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/rollback.py +0 -0
  57. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/status.py +0 -0
  58. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/commands/sync_certs.py +0 -0
  59. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/config.py +0 -0
  60. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/deploy.py +0 -0
  61. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/discovery.py +0 -0
  62. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/hooks.py +0 -0
  63. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/infra/__init__.py +0 -0
  64. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/infra/bluegreen.py +0 -0
  65. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/infra/templates.py +0 -0
  66. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/infra/utils.py +0 -0
  67. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/__init__.py +0 -0
  68. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/__init__.py +0 -0
  69. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/djaploy.py +0 -0
  70. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/restore_backup.py +0 -0
  71. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/sync_certs.py +0 -0
  72. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/update_certs.py +0 -0
  73. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/commands/verify.py +0 -0
  74. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/management/utils.py +0 -0
  75. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/notifications.py +0 -0
  76. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/utils.py +0 -0
  77. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/version.py +0 -0
  78. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy/versioning.py +0 -0
  79. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/SOURCES.txt +0 -0
  80. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/dependency_links.txt +0 -0
  81. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/entry_points.txt +0 -0
  82. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/requires.txt +0 -0
  83. {djaploy-1.2.7 → djaploy-1.2.8}/djaploy.egg-info/top_level.txt +0 -0
  84. {djaploy-1.2.7 → djaploy-1.2.8}/setup.cfg +0 -0
  85. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_borg.py +0 -0
  86. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_config.py +0 -0
  87. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_deploy_scripts.py +0 -0
  88. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_discovery.py +0 -0
  89. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_gunicornherder.py +0 -0
  90. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_hooks.py +0 -0
  91. {djaploy-1.2.7 → djaploy-1.2.8}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.7
3
+ Version: 1.2.8
4
4
  Summary: Modular Django deployment system based on pyinfra
5
5
  Author-email: Johanna Mae Dimayuga <johanna@techco.fi>
6
6
  License: MIT License
@@ -40,13 +40,13 @@ def start_services(host_data, artifact_path):
40
40
 
41
41
  # Only start if the service unit file exists — custom services
42
42
  # (e.g. streaming) may be rendered by project hooks, not djaploy core.
43
+ # Uses if/else so enable/restart failures still surface as errors.
43
44
  server.shell(
44
45
  name=f"Start {slot_service} if unit exists",
45
46
  commands=[
46
- f"test -f /etc/systemd/system/{slot_service}.service && "
47
- f"systemctl enable {slot_service} && "
48
- f"systemctl restart {slot_service} || "
49
- f"echo 'Unit {slot_service}.service not found, skipping'",
47
+ f"if [ -f /etc/systemd/system/{slot_service}.service ]; then "
48
+ f"systemctl enable {slot_service} && systemctl restart {slot_service}; "
49
+ f"else echo 'Unit {slot_service}.service not found, skipping'; fi",
50
50
  ],
51
51
  _sudo=True,
52
52
  )
@@ -61,7 +61,7 @@ def start_services(host_data, artifact_path):
61
61
  f"systemctl stop {app_name}.service 2>/dev/null || true",
62
62
  f"systemctl disable {app_name}.service 2>/dev/null || true",
63
63
  f"rm -f /etc/systemd/system/{app_name}.service",
64
- f"systemctl daemon-reload 2>/dev/null || true",
64
+ "systemctl daemon-reload 2>/dev/null || true",
65
65
  ],
66
66
  _sudo=True,
67
67
  )
@@ -760,6 +760,38 @@ def rollback_release(host_data, release=None):
760
760
  active_slot = (active_slot or "").strip()
761
761
  target_slot = other_slot(active_slot) if active_slot else "blue"
762
762
 
763
+ # Verify the target slot has a deployment before switching
764
+ server.shell(
765
+ name=f"Verify {target_slot} slot has a deployment for rollback",
766
+ commands=[
767
+ f"python3 -c \""
768
+ f"import json, sys; s=json.load(open('{state_file}')); "
769
+ f"sys.exit(0 if s['slots'].get('{target_slot}') else 1)"
770
+ f"\" || (echo 'ROLLBACK ABORTED: slot {target_slot} has no deployment.' && exit 1)",
771
+ ],
772
+ _sudo=True,
773
+ )
774
+
775
+ # Health check: verify the target slot is running before rolling back.
776
+ # Retries up to 3 times with 2s delay.
777
+ socket_path = get_slot_socket_path(app_name, target_slot)
778
+ server.shell(
779
+ name=f"Health check: verify {target_slot} slot is healthy for rollback",
780
+ commands=[
781
+ f"for attempt in 1 2 3; do "
782
+ f"HTTP_CODE=$(curl -o /dev/null -w '%{{http_code}}' --max-time 5 --unix-socket {socket_path} http://localhost/ 2>/dev/null); "
783
+ f"if [ \"$HTTP_CODE\" != \"000\" ]; then "
784
+ f"echo \"Health check passed: {target_slot} slot responding (HTTP $HTTP_CODE)\"; exit 0; fi; "
785
+ f"echo \"Health check attempt $attempt/3: no response, retrying in 2s...\"; "
786
+ f"sleep 2; "
787
+ f"done; "
788
+ f"echo 'ROLLBACK ABORTED: {target_slot} slot is not responding on {socket_path}' && "
789
+ f"echo 'Check: systemctl status {app_name}-{target_slot}.service' && "
790
+ f"exit 1",
791
+ ],
792
+ _sudo=True,
793
+ )
794
+
763
795
  # Store for activate:post hooks (timers, streaming, custom nginx)
764
796
  host.data._bluegreen_activated_slot = target_slot
765
797
 
@@ -913,20 +945,22 @@ def activate_bluegreen(host_data):
913
945
  _sudo=True,
914
946
  )
915
947
 
916
- # Extra gunicorn-specific check: verify the socket is responding
948
+ # Extra gunicorn-specific check: verify the socket accepts HTTP requests.
949
+ # Retries up to 3 times with 2s delay to allow freshly started gunicorn to boot.
917
950
  socket_path = get_slot_socket_path(app_name, target_slot)
918
951
  server.shell(
919
952
  name=f"Health check: verify {target_slot} gunicorn socket responds",
920
953
  commands=[
921
- f"if curl -sf --max-time 5 --unix-socket {socket_path} http://localhost/ > /dev/null 2>&1; then "
922
- f"echo 'Health check passed: gunicorn socket is responding'; "
923
- f"elif [ -S {socket_path} ]; then "
924
- f"echo 'Health check: socket exists, HTTP may be non-200 (proceeding)'; "
925
- f"else "
926
- f"echo 'ACTIVATION ABORTED: gunicorn socket not responding.' && "
927
- f"echo 'Socket: {socket_path}' && "
928
- f"exit 1; "
929
- f"fi",
954
+ f"for attempt in 1 2 3 4 5; do "
955
+ f"HTTP_CODE=$(curl -o /dev/null -w '%{{http_code}}' --max-time 5 --unix-socket {socket_path} http://localhost/ 2>/dev/null); "
956
+ f"if [ \"$HTTP_CODE\" != \"000\" ]; then "
957
+ f"echo \"Health check passed: gunicorn responding (HTTP $HTTP_CODE)\"; exit 0; fi; "
958
+ f"echo \"Health check attempt $attempt/5: no response, retrying in 3s...\"; "
959
+ f"sleep 3; "
960
+ f"done; "
961
+ f"echo 'ACTIVATION ABORTED: gunicorn not responding on {socket_path}' && "
962
+ f"echo 'Check: systemctl status {app_name}-{target_slot}.service' && "
963
+ f"exit 1",
930
964
  ],
931
965
  _sudo=True,
932
966
  )
@@ -981,7 +1015,7 @@ def activate_bluegreen(host_data):
981
1015
  print(f" Venv: {old_venv} -> {new_venv}")
982
1016
 
983
1017
  print(f"\n Previous slot ({p_slot}) is still running.")
984
- print(f" To rollback: manage.py djaploy rollback --env <env>\n")
1018
+ print(" To rollback: manage.py djaploy rollback --env <env>\n")
985
1019
 
986
1020
  python_op.call(
987
1021
  name="Print activation summary",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.7
3
+ Version: 1.2.8
4
4
  Summary: Modular Django deployment system based on pyinfra
5
5
  Author-email: Johanna Mae Dimayuga <johanna@techco.fi>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "djaploy"
3
- version = "1.2.7"
3
+ version = "1.2.8"
4
4
  description = "Modular Django deployment system based on pyinfra"
5
5
  authors = [
6
6
  {name = "Johanna Mae Dimayuga", email = "johanna@techco.fi"}
@@ -527,7 +527,7 @@ class TestBluegreenHealthCheck(unittest.TestCase):
527
527
  )
528
528
 
529
529
  # Must curl the correct socket
530
- self.assertIn(f"--unix-socket /run/testapp-green/testapp.sock", cmd)
530
+ self.assertIn("--unix-socket /run/testapp-green/testapp.sock", cmd)
531
531
  # Must check the correct service on fallback
532
532
  self.assertIn("systemctl is-active testapp-green.service", cmd)
533
533
  # Must abort with exit 1 on failure
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes