djaploy 1.2.2__tar.gz → 1.2.4__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.2 → djaploy-1.2.4}/PKG-INFO +1 -1
  2. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/deploy.py +3 -1
  3. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/rollback.py +10 -0
  4. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/infra/djaploy_hooks.py +87 -65
  5. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/PKG-INFO +1 -1
  6. {djaploy-1.2.2 → djaploy-1.2.4}/pyproject.toml +1 -1
  7. {djaploy-1.2.2 → djaploy-1.2.4}/LICENSE +0 -0
  8. {djaploy-1.2.2 → djaploy-1.2.4}/README.md +0 -0
  9. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/__init__.py +0 -0
  10. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/app.py +0 -0
  11. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/__init__.py +0 -0
  12. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/borg/__init__.py +0 -0
  13. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/borg/infra/__init__.py +0 -0
  14. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/borg/infra/djaploy_hooks.py +0 -0
  15. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/janitor/__init__.py +0 -0
  16. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/janitor/apps.py +0 -0
  17. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/janitor/infra/__init__.py +0 -0
  18. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/janitor/infra/commands/createjanitoruser.py +0 -0
  19. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/janitor/infra/djaploy_hooks.py +0 -0
  20. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/nginx/__init__.py +0 -0
  21. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/nginx/apps.py +0 -0
  22. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/nginx/infra/__init__.py +0 -0
  23. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/nginx/infra/djaploy_hooks.py +0 -0
  24. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/rclone/__init__.py +0 -0
  25. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/rclone/apps.py +0 -0
  26. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/rclone/infra/__init__.py +0 -0
  27. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/rclone/infra/djaploy_hooks.py +0 -0
  28. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/sync_certs/__init__.py +0 -0
  29. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/sync_certs/apps.py +0 -0
  30. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/sync_certs/infra/__init__.py +0 -0
  31. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/sync_certs/infra/djaploy_hooks.py +0 -0
  32. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/systemd/__init__.py +0 -0
  33. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/systemd/apps.py +0 -0
  34. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/systemd/infra/__init__.py +0 -0
  35. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/systemd/infra/djaploy_hooks.py +0 -0
  36. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/tailscale/__init__.py +0 -0
  37. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/tailscale/apps.py +0 -0
  38. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/tailscale/infra/__init__.py +0 -0
  39. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/tailscale/infra/djaploy_hooks.py +0 -0
  40. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/versioning/__init__.py +0 -0
  41. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/versioning/apps.py +0 -0
  42. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/versioning/infra/__init__.py +0 -0
  43. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/apps/versioning/infra/djaploy_hooks.py +0 -0
  44. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/artifact.py +0 -0
  45. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/bin/__init__.py +0 -0
  46. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/bin/django_pyinfra.py +0 -0
  47. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/bin/gunicornherder.py +0 -0
  48. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/builtin_hooks.py +0 -0
  49. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/certificates.py +0 -0
  50. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/changelog.py +0 -0
  51. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/__init__.py +0 -0
  52. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/_utils.py +0 -0
  53. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/activate.py +0 -0
  54. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/configure.py +0 -0
  55. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/restore.py +0 -0
  56. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/status.py +0 -0
  57. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/commands/sync_certs.py +0 -0
  58. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/config.py +0 -0
  59. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/deploy.py +0 -0
  60. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/discovery.py +0 -0
  61. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/hooks.py +0 -0
  62. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/infra/__init__.py +0 -0
  63. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/infra/bluegreen.py +0 -0
  64. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/infra/templates.py +0 -0
  65. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/infra/utils.py +0 -0
  66. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/__init__.py +0 -0
  67. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/__init__.py +0 -0
  68. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/djaploy.py +0 -0
  69. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/restore_backup.py +0 -0
  70. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/sync_certs.py +0 -0
  71. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/update_certs.py +0 -0
  72. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/commands/verify.py +0 -0
  73. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/management/utils.py +0 -0
  74. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/notifications.py +0 -0
  75. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/utils.py +0 -0
  76. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/version.py +0 -0
  77. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy/versioning.py +0 -0
  78. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/SOURCES.txt +0 -0
  79. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/dependency_links.txt +0 -0
  80. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/entry_points.txt +0 -0
  81. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/requires.txt +0 -0
  82. {djaploy-1.2.2 → djaploy-1.2.4}/djaploy.egg-info/top_level.txt +0 -0
  83. {djaploy-1.2.2 → djaploy-1.2.4}/setup.cfg +0 -0
  84. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_bluegreen.py +0 -0
  85. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_borg.py +0 -0
  86. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_config.py +0 -0
  87. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_deploy_scripts.py +0 -0
  88. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_discovery.py +0 -0
  89. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_gunicornherder.py +0 -0
  90. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_hooks.py +0 -0
  91. {djaploy-1.2.2 → djaploy-1.2.4}/tests/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -34,7 +34,9 @@ for phase in ("deploy:upload", "deploy:configure", "deploy:pre", "deploy:start")
34
34
  _activate_val = getattr(host.data, "activate", None)
35
35
  if _activate_val in (True, "true", "True"):
36
36
  for phase in ("activate:pre", "activate", "activate:post"):
37
- for hook in registry.get_remote_hooks(phase):
37
+ _hooks = registry.get_remote_hooks(phase)
38
+ print(f"[activate] phase={phase} hooks={[h.function.__name__ for h in _hooks]}")
39
+ for hook in _hooks:
38
40
  _deploy_decorator(hook.function.__name__)(hook.function)(
39
41
  host.data
40
42
  )
@@ -24,3 +24,13 @@ for hook in registry.get_remote_hooks("rollback"):
24
24
  _deploy_decorator(hook.function.__name__)(hook.function)(
25
25
  host.data, release
26
26
  )
27
+
28
+ # For bluegreen rollback, run activate:post hooks to update
29
+ # custom nginx upstream, timers, etc.
30
+ from djaploy.infra.utils import is_bluegreen
31
+ if is_bluegreen(host.data):
32
+ for phase in ("activate:post",):
33
+ for hook in registry.get_remote_hooks(phase):
34
+ _deploy_decorator(hook.function.__name__)(hook.function)(
35
+ host.data
36
+ )
@@ -712,61 +712,74 @@ def rollback_release(host_data, release=None):
712
712
  app_path = get_app_path(host_data)
713
713
 
714
714
  if is_bluegreen(host_data):
715
+ from pyinfra import host
716
+ from pyinfra.facts.server import Command
715
717
  from djaploy.infra.bluegreen import (
716
718
  read_active_slot_cmd, set_active_slot_cmd, other_slot,
717
719
  )
718
720
  from djaploy.infra.utils import get_slot_socket_path
719
721
  from io import StringIO
720
722
  from djaploy.infra.templates import NGINX_UPSTREAM_BLUEGREEN, build_template_context
723
+ from jinja2 import Environment
721
724
 
722
725
  app_name = getattr(host_data, 'app_name', None)
723
726
  state_file = f"{app_path}/state.json"
724
727
 
725
- # Read active slot and switch to the other one
728
+ # Read active slot and compute target
729
+ active_slot = host.get_fact(Command, read_active_slot_cmd(state_file))
730
+ active_slot = (active_slot or "").strip()
731
+ target_slot = other_slot(active_slot) if active_slot else "blue"
732
+
733
+ # Store for activate:post hooks (timers, streaming, custom nginx)
734
+ host.data._bluegreen_activated_slot = target_slot
735
+
736
+ # Update nginx upstream for non-custom setups
737
+ ctx = build_template_context(host_data)
738
+ ctx["active_slot"] = target_slot
739
+ upstream_content = Environment().from_string(NGINX_UPSTREAM_BLUEGREEN).render(**ctx)
740
+
741
+ nginx_cfg = getattr(host_data, 'nginx_conf', None) or {}
742
+ if not nginx_cfg.get("custom"):
743
+ from pyinfra.operations import files
744
+ files.put(
745
+ name=f"Update nginx upstream to {target_slot} slot",
746
+ src=StringIO(upstream_content),
747
+ dest=f"/etc/nginx/sites-available/{app_name}-upstream.conf",
748
+ _sudo=True,
749
+ )
750
+
751
+ # Reload nginx
726
752
  server.shell(
727
- name="Roll back blue-green: switch to other slot",
728
- commands=[
729
- # Read current active slot, compute target, verify it has a deployment
730
- f'ACTIVE=$({read_active_slot_cmd(state_file)}) && '
731
- f'if [ "$ACTIVE" = "blue" ]; then TARGET=green; else TARGET=blue; fi && '
732
- f'echo "Rolling back: switching from $ACTIVE to $TARGET"',
733
- ],
753
+ name="Test and reload nginx for rollback",
754
+ commands=["nginx -t && nginx -s reload"],
755
+ _sudo=True,
756
+ )
757
+
758
+ # Update state.json
759
+ server.shell(
760
+ name=f"Set active slot to {target_slot} (rollback)",
761
+ commands=[set_active_slot_cmd(state_file, target_slot)],
734
762
  _sudo=True,
735
763
  _sudo_user=app_user,
736
764
  )
737
765
 
738
- # Update nginx upstream to the other slot and reload
739
- # We render both variants and use a shell command to pick the right one
740
- for target_slot in ("blue", "green"):
741
- ctx = build_template_context(host_data)
742
- ctx["active_slot"] = target_slot
743
- from jinja2 import Environment
744
- upstream_content = Environment().from_string(NGINX_UPSTREAM_BLUEGREEN).render(**ctx)
766
+ # Print summary
767
+ from pyinfra.operations import python as python_op
745
768
 
746
- server.shell(
747
- name=f"Prepare nginx upstream for {target_slot}",
748
- commands=[
749
- f'ACTIVE=$({read_active_slot_cmd(state_file)}) && '
750
- f'if [ "$ACTIVE" = "blue" ]; then TARGET=green; else TARGET=blue; fi && '
751
- f'if [ "$TARGET" = "{target_slot}" ]; then '
752
- f"cat > /etc/nginx/sites-available/{app_name}-upstream.conf << 'NGINX_EOF'\n{upstream_content}NGINX_EOF\n"
753
- f'fi',
754
- ],
755
- _sudo=True,
756
- )
769
+ def _print_rollback_summary(state_f, t_slot, p_slot):
770
+ info = _read_slot_info_from_remote(host, t_slot, state_f)
771
+ print(f"\n=== Blue-Green Rollback ===")
772
+ print(f" Switched: {p_slot} -> {t_slot}")
773
+ print(f" Release: {info.get('release', 'unknown')}")
774
+ print(f" Python: {info.get('python_interpreter', 'unknown')}")
775
+ print()
757
776
 
758
- server.shell(
759
- name="Reload nginx and update state for rollback",
760
- commands=[
761
- f'nginx -t && nginx -s reload',
762
- f'ACTIVE=$({read_active_slot_cmd(state_file)}) && '
763
- f'if [ "$ACTIVE" = "blue" ]; then TARGET=green; else TARGET=blue; fi && '
764
- + set_active_slot_cmd(state_file, '" + "$TARGET" + "').replace(
765
- "'" + '"' + " + " + '"' + "$TARGET" + '"' + " + " + '"' + "'",
766
- "'\"$TARGET\"'"
767
- ),
768
- ],
769
- _sudo=True,
777
+ python_op.call(
778
+ name="Print rollback summary",
779
+ function=_print_rollback_summary,
780
+ state_f=state_file,
781
+ t_slot=target_slot,
782
+ p_slot=active_slot or "none",
770
783
  )
771
784
  return
772
785
 
@@ -822,37 +835,44 @@ def activate_bluegreen(host_data):
822
835
  app_path = get_app_path(host_data)
823
836
  state_file = f"{app_path}/state.json"
824
837
 
825
- # Read current active slot
826
- active_slot = host.get_fact(
827
- Command,
828
- read_active_slot_cmd(state_file),
829
- )
830
- active_slot = (active_slot or "").strip()
831
- target_slot = other_slot(active_slot) if active_slot else "blue"
832
-
833
- # Verify target slot has a deployment
834
- has_deploy = host.get_fact(
835
- Command,
836
- f"python3 -c \""
837
- f"import json; s=json.load(open('{state_file}')); "
838
- f"print('yes' if s['slots'].get('{target_slot}') else 'no')"
839
- f"\"",
840
- )
841
- if (has_deploy or "").strip() != "yes":
842
- # Already on the latest slot — nothing to activate.
843
- # Print a message but don't fail the command.
838
+ # If called from deploy --activate, the target slot is already known
839
+ # and we can skip the stale-fact verification.
840
+ deploy_target = getattr(host.data, '_bluegreen_target_slot', None)
841
+ if deploy_target:
842
+ # deploy --activate: we just deployed to this slot, activate it
843
+ active_slot_raw = host.get_fact(
844
+ Command,
845
+ read_active_slot_cmd(state_file),
846
+ )
847
+ active_slot = (active_slot_raw or "").strip()
848
+ target_slot = deploy_target
849
+ else:
850
+ # Standalone activate: read state and switch to the other slot
851
+ active_slot_raw = host.get_fact(
852
+ Command,
853
+ read_active_slot_cmd(state_file),
854
+ )
855
+ active_slot = (active_slot_raw or "").strip()
856
+ target_slot = other_slot(active_slot) if active_slot else "blue"
857
+
858
+ # Verify target slot has a deployment (use server.shell to avoid
859
+ # fact caching issues — the check runs at execution time, not plan time)
860
+ current_display = active_slot or "none"
844
861
  server.shell(
845
- name="Nothing to activate",
862
+ name=f"Verify {target_slot} slot has a deployment",
846
863
  commands=[
847
- f'echo ""',
848
- f'echo "Nothing to activate: slot {target_slot} has no deployment."',
849
- f'echo "Current active slot: {active_slot or "none"}"',
850
- f'echo "Run \'djaploy deploy\' first to stage a new version."',
851
- f'echo ""',
864
+ f"python3 -c \""
865
+ f"import json, sys; s=json.load(open('{state_file}')); "
866
+ f"sys.exit(0 if s['slots'].get('{target_slot}') else 1)"
867
+ f"\" || (echo 'Nothing to activate: slot {target_slot} has no deployment.' && "
868
+ f"echo 'Current active slot: {current_display}' && "
869
+ f"echo 'Run djaploy deploy first to stage a new version.' && exit 1)",
852
870
  ],
853
871
  _sudo=True,
854
872
  )
855
- return
873
+
874
+ # Store on host.data so activate:post hooks can read it
875
+ host.data._bluegreen_activated_slot = target_slot
856
876
 
857
877
  # Update nginx upstream config to point to target slot
858
878
  from io import StringIO
@@ -863,9 +883,11 @@ def activate_bluegreen(host_data):
863
883
  ctx["active_slot"] = target_slot
864
884
  upstream_content = Environment().from_string(NGINX_UPSTREAM_BLUEGREEN).render(**ctx)
865
885
 
886
+ # Write the upstream config. Custom nginx setups (e.g. bostad) handle
887
+ # their own upstream file via activate:post hooks.
888
+ from pyinfra.operations import files
866
889
  nginx_cfg = getattr(host_data, 'nginx_conf', None) or {}
867
890
  if not nginx_cfg.get("custom"):
868
- from pyinfra.operations import files
869
891
  files.put(
870
892
  name=f"Update nginx upstream to {target_slot} slot",
871
893
  src=StringIO(upstream_content),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djaploy
3
- Version: 1.2.2
3
+ Version: 1.2.4
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.2"
3
+ version = "1.2.4"
4
4
  description = "Modular Django deployment system based on pyinfra"
5
5
  authors = [
6
6
  {name = "Johanna Mae Dimayuga", email = "johanna@techco.fi"}
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
File without changes