djaploy 1.2.3__tar.gz → 1.2.5__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.
- {djaploy-1.2.3 → djaploy-1.2.5}/PKG-INFO +1 -1
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/borg/infra/djaploy_hooks.py +24 -2
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/rollback.py +10 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/infra/djaploy_hooks.py +85 -65
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/PKG-INFO +1 -1
- {djaploy-1.2.3 → djaploy-1.2.5}/pyproject.toml +1 -1
- {djaploy-1.2.3 → djaploy-1.2.5}/LICENSE +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/README.md +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/app.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/borg/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/borg/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/janitor/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/janitor/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/janitor/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/janitor/infra/commands/createjanitoruser.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/janitor/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/nginx/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/nginx/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/nginx/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/nginx/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/rclone/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/rclone/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/rclone/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/rclone/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/sync_certs/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/sync_certs/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/sync_certs/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/sync_certs/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/systemd/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/systemd/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/systemd/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/systemd/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/tailscale/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/tailscale/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/tailscale/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/tailscale/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/versioning/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/versioning/apps.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/versioning/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/apps/versioning/infra/djaploy_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/artifact.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/bin/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/bin/django_pyinfra.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/bin/gunicornherder.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/builtin_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/certificates.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/changelog.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/_utils.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/activate.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/configure.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/deploy.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/restore.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/status.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/commands/sync_certs.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/config.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/deploy.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/discovery.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/infra/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/infra/bluegreen.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/infra/templates.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/infra/utils.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/__init__.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/djaploy.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/restore_backup.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/sync_certs.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/update_certs.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/commands/verify.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/management/utils.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/notifications.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/utils.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/version.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy/versioning.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/SOURCES.txt +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/dependency_links.txt +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/entry_points.txt +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/requires.txt +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/djaploy.egg-info/top_level.txt +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/setup.cfg +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_bluegreen.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_borg.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_config.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_deploy_scripts.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_discovery.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_gunicornherder.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_hooks.py +0 -0
- {djaploy-1.2.3 → djaploy-1.2.5}/tests/test_versioning.py +0 -0
|
@@ -146,7 +146,7 @@ def _generate_backup_script(borg_config, app_user: str, repo_name: str,
|
|
|
146
146
|
if not isinstance(host_data, dict)
|
|
147
147
|
else host_data.get('deployment_strategy', 'zero_downtime')
|
|
148
148
|
)
|
|
149
|
-
if deployment_strategy
|
|
149
|
+
if deployment_strategy in ('zero_downtime', 'bluegreen'):
|
|
150
150
|
default_media = f"/home/{app_user}/apps/{app_name}/shared/media"
|
|
151
151
|
else:
|
|
152
152
|
default_media = f"/home/{app_user}/apps/{app_name}/media"
|
|
@@ -527,7 +527,7 @@ def restore_borg(host_data, restore_opts):
|
|
|
527
527
|
if not isinstance(host_data, dict)
|
|
528
528
|
else host_data.get('deployment_strategy', 'zero_downtime')
|
|
529
529
|
)
|
|
530
|
-
if deployment_strategy
|
|
530
|
+
if deployment_strategy in ('zero_downtime', 'bluegreen'):
|
|
531
531
|
default_media = f"/home/{app_user}/apps/{app_name}/shared/media"
|
|
532
532
|
else:
|
|
533
533
|
default_media = f"/home/{app_user}/apps/{app_name}/media"
|
|
@@ -540,6 +540,28 @@ def restore_borg(host_data, restore_opts):
|
|
|
540
540
|
db_only = restore_opts.get("db_only", False)
|
|
541
541
|
services = getattr(host_data, "services", None) or []
|
|
542
542
|
|
|
543
|
+
# For bluegreen, expand service names to per-slot services and include
|
|
544
|
+
# per-slot streaming services so they are also stopped during restore.
|
|
545
|
+
deployment_strategy = (
|
|
546
|
+
getattr(host_data, 'deployment_strategy', 'zero_downtime')
|
|
547
|
+
if not isinstance(host_data, dict)
|
|
548
|
+
else host_data.get('deployment_strategy', 'zero_downtime')
|
|
549
|
+
)
|
|
550
|
+
if deployment_strategy == 'bluegreen':
|
|
551
|
+
app_name = (
|
|
552
|
+
getattr(host_data, 'app_name', '')
|
|
553
|
+
if not isinstance(host_data, dict)
|
|
554
|
+
else host_data.get('app_name', '')
|
|
555
|
+
)
|
|
556
|
+
bluegreen_services = []
|
|
557
|
+
for svc in services:
|
|
558
|
+
bluegreen_services.append(f"{app_name}-blue")
|
|
559
|
+
bluegreen_services.append(f"{app_name}-green")
|
|
560
|
+
# Also stop streaming coordinator instances
|
|
561
|
+
bluegreen_services.append(f"{app_name}-streaming-blue")
|
|
562
|
+
bluegreen_services.append(f"{app_name}-streaming-green")
|
|
563
|
+
services = bluegreen_services
|
|
564
|
+
|
|
543
565
|
# Repo connection details come from the *source* borg config, but
|
|
544
566
|
# ssh_known_hosts_file must come from the *target* (the file lives on
|
|
545
567
|
# the machine running borg, not the machine the backup was taken from).
|
|
@@ -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
|
+
)
|
|
@@ -160,6 +160,7 @@ def configure_server(host_data):
|
|
|
160
160
|
_sudo_user=app_user,
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
+
|
|
163
164
|
shared_resources = getattr(host_data, 'shared_resources', None) or []
|
|
164
165
|
if shared_resources:
|
|
165
166
|
mkdir_commands = [
|
|
@@ -712,61 +713,74 @@ def rollback_release(host_data, release=None):
|
|
|
712
713
|
app_path = get_app_path(host_data)
|
|
713
714
|
|
|
714
715
|
if is_bluegreen(host_data):
|
|
716
|
+
from pyinfra import host
|
|
717
|
+
from pyinfra.facts.server import Command
|
|
715
718
|
from djaploy.infra.bluegreen import (
|
|
716
719
|
read_active_slot_cmd, set_active_slot_cmd, other_slot,
|
|
717
720
|
)
|
|
718
721
|
from djaploy.infra.utils import get_slot_socket_path
|
|
719
722
|
from io import StringIO
|
|
720
723
|
from djaploy.infra.templates import NGINX_UPSTREAM_BLUEGREEN, build_template_context
|
|
724
|
+
from jinja2 import Environment
|
|
721
725
|
|
|
722
726
|
app_name = getattr(host_data, 'app_name', None)
|
|
723
727
|
state_file = f"{app_path}/state.json"
|
|
724
728
|
|
|
725
|
-
# Read active slot and
|
|
729
|
+
# Read active slot and compute target
|
|
730
|
+
active_slot = host.get_fact(Command, read_active_slot_cmd(state_file))
|
|
731
|
+
active_slot = (active_slot or "").strip()
|
|
732
|
+
target_slot = other_slot(active_slot) if active_slot else "blue"
|
|
733
|
+
|
|
734
|
+
# Store for activate:post hooks (timers, streaming, custom nginx)
|
|
735
|
+
host.data._bluegreen_activated_slot = target_slot
|
|
736
|
+
|
|
737
|
+
# Update nginx upstream for non-custom setups
|
|
738
|
+
ctx = build_template_context(host_data)
|
|
739
|
+
ctx["active_slot"] = target_slot
|
|
740
|
+
upstream_content = Environment().from_string(NGINX_UPSTREAM_BLUEGREEN).render(**ctx)
|
|
741
|
+
|
|
742
|
+
nginx_cfg = getattr(host_data, 'nginx_conf', None) or {}
|
|
743
|
+
if not nginx_cfg.get("custom"):
|
|
744
|
+
from pyinfra.operations import files
|
|
745
|
+
files.put(
|
|
746
|
+
name=f"Update nginx upstream to {target_slot} slot",
|
|
747
|
+
src=StringIO(upstream_content),
|
|
748
|
+
dest=f"/etc/nginx/sites-available/{app_name}-upstream.conf",
|
|
749
|
+
_sudo=True,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
# Reload nginx
|
|
726
753
|
server.shell(
|
|
727
|
-
name="
|
|
728
|
-
commands=[
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
754
|
+
name="Test and reload nginx for rollback",
|
|
755
|
+
commands=["nginx -t && nginx -s reload"],
|
|
756
|
+
_sudo=True,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
# Update state.json
|
|
760
|
+
server.shell(
|
|
761
|
+
name=f"Set active slot to {target_slot} (rollback)",
|
|
762
|
+
commands=[set_active_slot_cmd(state_file, target_slot)],
|
|
734
763
|
_sudo=True,
|
|
735
764
|
_sudo_user=app_user,
|
|
736
765
|
)
|
|
737
766
|
|
|
738
|
-
#
|
|
739
|
-
|
|
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)
|
|
767
|
+
# Print summary
|
|
768
|
+
from pyinfra.operations import python as python_op
|
|
745
769
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
f'fi',
|
|
754
|
-
],
|
|
755
|
-
_sudo=True,
|
|
756
|
-
)
|
|
770
|
+
def _print_rollback_summary(state_f, t_slot, p_slot):
|
|
771
|
+
info = _read_slot_info_from_remote(host, t_slot, state_f)
|
|
772
|
+
print(f"\n=== Blue-Green Rollback ===")
|
|
773
|
+
print(f" Switched: {p_slot} -> {t_slot}")
|
|
774
|
+
print(f" Release: {info.get('release', 'unknown')}")
|
|
775
|
+
print(f" Python: {info.get('python_interpreter', 'unknown')}")
|
|
776
|
+
print()
|
|
757
777
|
|
|
758
|
-
|
|
759
|
-
name="
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
+ set_active_slot_cmd(state_file, '" + "$TARGET" + "').replace(
|
|
765
|
-
"'" + '"' + " + " + '"' + "$TARGET" + '"' + " + " + '"' + "'",
|
|
766
|
-
"'\"$TARGET\"'"
|
|
767
|
-
),
|
|
768
|
-
],
|
|
769
|
-
_sudo=True,
|
|
778
|
+
python_op.call(
|
|
779
|
+
name="Print rollback summary",
|
|
780
|
+
function=_print_rollback_summary,
|
|
781
|
+
state_f=state_file,
|
|
782
|
+
t_slot=target_slot,
|
|
783
|
+
p_slot=active_slot or "none",
|
|
770
784
|
)
|
|
771
785
|
return
|
|
772
786
|
|
|
@@ -822,37 +836,41 @@ def activate_bluegreen(host_data):
|
|
|
822
836
|
app_path = get_app_path(host_data)
|
|
823
837
|
state_file = f"{app_path}/state.json"
|
|
824
838
|
|
|
825
|
-
#
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
839
|
+
# If called from deploy --activate, the target slot is already known
|
|
840
|
+
# and we can skip the stale-fact verification.
|
|
841
|
+
deploy_target = getattr(host.data, '_bluegreen_target_slot', None)
|
|
842
|
+
if deploy_target:
|
|
843
|
+
# deploy --activate: we just deployed to this slot, activate it
|
|
844
|
+
active_slot_raw = host.get_fact(
|
|
845
|
+
Command,
|
|
846
|
+
read_active_slot_cmd(state_file),
|
|
847
|
+
)
|
|
848
|
+
active_slot = (active_slot_raw or "").strip()
|
|
849
|
+
target_slot = deploy_target
|
|
850
|
+
else:
|
|
851
|
+
# Standalone activate: read state and switch to the other slot
|
|
852
|
+
active_slot_raw = host.get_fact(
|
|
853
|
+
Command,
|
|
854
|
+
read_active_slot_cmd(state_file),
|
|
855
|
+
)
|
|
856
|
+
active_slot = (active_slot_raw or "").strip()
|
|
857
|
+
target_slot = other_slot(active_slot) if active_slot else "blue"
|
|
858
|
+
|
|
859
|
+
# Verify target slot has a deployment (use server.shell to avoid
|
|
860
|
+
# fact caching issues — the check runs at execution time, not plan time)
|
|
861
|
+
current_display = active_slot or "none"
|
|
844
862
|
server.shell(
|
|
845
|
-
name="
|
|
863
|
+
name=f"Verify {target_slot} slot has a deployment",
|
|
846
864
|
commands=[
|
|
847
|
-
f
|
|
848
|
-
f
|
|
849
|
-
f
|
|
850
|
-
f
|
|
851
|
-
f
|
|
865
|
+
f"python3 -c \""
|
|
866
|
+
f"import json, sys; s=json.load(open('{state_file}')); "
|
|
867
|
+
f"sys.exit(0 if s['slots'].get('{target_slot}') else 1)"
|
|
868
|
+
f"\" || (echo 'Nothing to activate: slot {target_slot} has no deployment.' && "
|
|
869
|
+
f"echo 'Current active slot: {current_display}' && "
|
|
870
|
+
f"echo 'Run djaploy deploy first to stage a new version.' && exit 1)",
|
|
852
871
|
],
|
|
853
872
|
_sudo=True,
|
|
854
873
|
)
|
|
855
|
-
return
|
|
856
874
|
|
|
857
875
|
# Store on host.data so activate:post hooks can read it
|
|
858
876
|
host.data._bluegreen_activated_slot = target_slot
|
|
@@ -866,9 +884,11 @@ def activate_bluegreen(host_data):
|
|
|
866
884
|
ctx["active_slot"] = target_slot
|
|
867
885
|
upstream_content = Environment().from_string(NGINX_UPSTREAM_BLUEGREEN).render(**ctx)
|
|
868
886
|
|
|
887
|
+
# Write the upstream config. Custom nginx setups (e.g. bostad) handle
|
|
888
|
+
# their own upstream file via activate:post hooks.
|
|
889
|
+
from pyinfra.operations import files
|
|
869
890
|
nginx_cfg = getattr(host_data, 'nginx_conf', None) or {}
|
|
870
891
|
if not nginx_cfg.get("custom"):
|
|
871
|
-
from pyinfra.operations import files
|
|
872
892
|
files.put(
|
|
873
893
|
name=f"Update nginx upstream to {target_slot} slot",
|
|
874
894
|
src=StringIO(upstream_content),
|
|
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
|
|
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
|
|
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
|
|
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
|