Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storing Git config in tenant model #391

12 changes: 12 additions & 0 deletions backend/src/zango/api/platform/tenancy/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ def get_date_format_display(self, obj):
return obj.get_date_format_display()

def update(self, instance, validated_data):
request = self.context["request"]
extra_config_str = request.data.get("extra_config")
# Convert extra_config from string to JSON if it exists
if extra_config_str:
try:
extra_config_json = json.loads(extra_config_str)
validated_data["extra_config"] = extra_config_json
except json.JSONDecodeError:
raise serializers.ValidationError(
{"extra_config": "Invalid JSON format"}
)

instance = super(TenantSerializerModel, self).update(instance, validated_data)
request = self.context["request"]
domains = request.data.getlist("domains")
Expand Down
40 changes: 40 additions & 0 deletions backend/src/zango/api/platform/tenancy/v1/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import traceback

from django_celery_results.models import TaskResult
Expand All @@ -10,6 +11,7 @@
from zango.apps.permissions.models import PolicyModel
from zango.apps.shared.tenancy.models import TenantModel, ThemesModel
from zango.apps.shared.tenancy.utils import DATEFORMAT, DATETIMEFORMAT, TIMEZONES
from zango.cli.git_setup import git_setup
from zango.core.api import (
ZangoGenericPlatformAPIView,
get_api_response,
Expand Down Expand Up @@ -164,9 +166,16 @@ def get(self, request, *args, **kwargs):

return get_api_response(success, response, status)

def get_branch(self, config, key, default=None):
branch = config.get("branch", {}).get(key, default)
return branch if branch else default

def put(self, request, *args, **kwargs):
try:
obj = self.get_obj(**kwargs)
old_git_config = (
obj.extra_config.get("git_config", {}) if obj.extra_config else {}
)
serializer = TenantSerializerModel(
instance=obj,
data=request.data,
Expand All @@ -177,6 +186,34 @@ def put(self, request, *args, **kwargs):
serializer.save()
success = True
status_code = 200
if serializer.data.get("extra_config", None):
app_directory = os.path.join(os.getcwd(), "workspaces", obj.name)
new_git_config = serializer.data["extra_config"].get(
"git_config", {}
)

new_repo_url = new_git_config.get("repo_url")
old_repo_url = old_git_config.get("repo_url")

if new_repo_url and (
not old_git_config or new_repo_url != old_repo_url
):
git_setup(
[
app_directory,
"--git_repo_url",
new_repo_url,
"--dev_branch",
self.get_branch(new_git_config, "dev", "development"),
"--staging_branch",
self.get_branch(new_git_config, "staging", "staging"),
"--prod_branch",
self.get_branch(new_git_config, "prod", "main"),
"--initialize",
],
standalone_mode=False,
)

result = {
"message": "App Settings Updated Successfully",
"app_uuid": str(obj.uuid),
Expand All @@ -195,6 +232,9 @@ def put(self, request, *args, **kwargs):

result = {"message": error_message}
except Exception as e:
import traceback

print(traceback.format_exc())
success = False
result = {"message": str(e)}
status_code = 500
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from django_tenants.management.commands.migrate_schemas import MigrateSchemasCommand

from django.conf import settings
Expand Down Expand Up @@ -45,8 +47,18 @@ def handle(self, *args, **options):
"dynamic_models": f"workspaces.{ options['workspace']}.migrations"
}
else:
settings.MIGRATION_MODULES = {
"dynamic_models": f"workspaces.{ options['workspace']}.packages.{options['package']}.migrations"
}
if os.path.exists(
f"workspaces/{options['workspace']}/packages/{options['package']}/migrations"
):
settings.MIGRATION_MODULES = {
"dynamic_models": f"workspaces.{ options['workspace']}.packages.{options['package']}.migrations"
}
else:
self.stdout.write(
self.style.NOTICE(
f"\n\nThe package '{options['package']}' does not have any migrations. Please ensure that you have entered the correct package name and try again."
)
)
exit(0)
options["schema_name"] = tenant_obj.schema_name
super().handle(*args, **options)
44 changes: 32 additions & 12 deletions backend/src/zango/cli/git_setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import json
import os
import sys

import click
import git

import django

from .update_apps import find_project_name


def is_valid_app_directory(directory):
# Define your validation criteria
Expand All @@ -20,7 +25,7 @@ def update_settings_with_git_repo_url(
app_directory, git_repo_url, dev_branch, staging_branch, prod_branch
):
"""
Update the 'git_repo_url' in the 'settings.json' file located in the app directory.
Update the 'git_repo_url' in the TenantMode.extra_config field.

Args:
- app_directory (str): Full path of the app directory.
Expand All @@ -32,32 +37,44 @@ def update_settings_with_git_repo_url(
settings_file_path = os.path.join(app_directory, "settings.json")

try:
project_name = find_project_name()
project_root = os.getcwd()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{project_name}.settings")
sys.path.insert(0, project_root)
django.setup()

from zango.apps.shared.tenancy.models import TenantModel

# Load current settings from settings.json
with open(settings_file_path, "r") as settings_file:
settings = json.load(settings_file)

# Update git_repo_url in settings
git_config = settings.get("git_config", {})
git_config["repo_url"] = git_repo_url
app_name = settings.get("app_name", "")
tenant_obj = TenantModel.objects.get(name=app_name)
git_config = {}
if tenant_obj.extra_config:
git_config = tenant_obj.extra_config.get("git_config", {})
else:
git_config["repo_url"] = git_repo_url
git_branch = git_config.get("branch", {})
git_branch.update(
{"dev": dev_branch, "staging": staging_branch, "prod": prod_branch}
)
git_config["branch"] = git_branch
settings["git_config"] = git_config

# Write updated settings back to settings.json
with open(settings_file_path, "w") as settings_file:
json.dump(settings, settings_file, indent=4)
if tenant_obj.extra_config:
tenant_obj.extra_config.update({"git_config": git_config})
else:
tenant_obj.extra_config = {"git_config": git_config}
tenant_obj.save()

return True

except FileNotFoundError:
click.echo(f"Error: settings.json not found in {app_directory}.")
except json.JSONDecodeError:
click.echo(f"Error: settings.json is not valid JSON in {app_directory}.")
except Exception as e:
click.echo(f"Error occurred while updating settings.json: {e}")
click.echo(f"Error occurred while updating tenant extra config: {e}")

return False

Expand Down Expand Up @@ -115,6 +132,8 @@ def git_setup(

try:
if initialize:
os.system(f"rm -rf {app_directory}/.git")
os.system("rm -rf .gitignore")
# Initialize git repository
repo = git.Repo.init(app_directory)

Expand All @@ -140,8 +159,9 @@ def git_setup(

# Check if the branch exists locally
if dev_branch in remote_branches:
# Checkout the existing branch
repo.git.checkout(dev_branch)
raise Exception(
"Can't initialize repository with existing remote branches with same name."
)
else:
# Create a new branch and checkout
repo.git.checkout("-b", dev_branch)
Expand Down
31 changes: 20 additions & 11 deletions backend/src/zango/cli/update_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,15 @@ def run_app_migrations(tenant, app_directory):
subprocess.run(["python", "manage.py", "ws_migrate", tenant], check=True)
print("Migrations ran successfully.")
except subprocess.CalledProcessError:
print("Migrations failed.")
raise
raise Exception("Migrations failed.")


def sync_static(tenant):
try:
subprocess.run(["python", "manage.py", "sync_static", tenant], check=True)
print("Static files collected successfully.")
except subprocess.CalledProcessError:
print("Collecting static files failed.")
raise
raise Exception("Collecting static files failed.")


def collect_static():
Expand Down Expand Up @@ -220,6 +218,7 @@ def execute_fixtures(tenant_name, last_version, current_version, app_directory):
bold=True,
)
click.echo(error_message, err=True)
raise Exception(message)

return failed_fixture_dict

Expand Down Expand Up @@ -309,6 +308,7 @@ def create_release(tenant_name, app_settings, app_directory, git_mode):
tenant = TenantModel.objects.get(name=tenant_name)
connection.set_tenant(tenant)
with connection.cursor() as c:
release = None
try:
current_version = app_settings.get("version")
if not current_version:
Expand Down Expand Up @@ -378,10 +378,13 @@ def create_release(tenant_name, app_settings, app_directory, git_mode):
print("No version change detected for")

except Exception as e:
if release:
release.status = "failed"
release.save()
import traceback

print(traceback.format_exc())
print(f"An error occurred while creating/updating release: {e}")
raise Exception(f"An error occurred while creating/updating release: {e}")


def is_update_allowed(tenant, app_settings, git_mode=False, repo_url=None, branch=None):
Expand Down Expand Up @@ -422,8 +425,10 @@ def is_update_allowed(tenant, app_settings, git_mode=False, repo_url=None, branc
return False, "Invalid Zango version specifier in settings.json"

if git_mode:
if is_version_greater(remote_version, local_version):
return False, "Remote version is greater than local version"
if not (
is_version_greater(remote_version, local_version)
is_version_greater(local_version, remote_version)
or (
last_release
and is_version_greater(remote_version, last_release.version)
Expand Down Expand Up @@ -486,18 +491,19 @@ def update_apps(app_name):
repo_url = None
branch = None
git_mode = False
if app_settings.get("git_config"):
git_settings = tenant_obj.extra_config.get("git_config")
if git_settings:
git_mode = True
# Initialize git repository
repo_url = app_settings["git_config"]["repo_url"]
repo_url = git_settings["repo_url"]

# Split the repo URL into parts
parts = repo_url.split("://")

# Add username and password to the URL
repo_url = f"{parts[0]}://{settings.GIT_USERNAME}:{settings.GIT_PASSWORD}@{parts[1]}"

branch = app_settings["git_config"]["branch"].get(settings.ENV, "main")
branch = git_settings["branch"].get(settings.ENV, "main")

update_allowed, message = is_update_allowed(
tenant, app_settings, git_mode, repo_url, branch
Expand Down Expand Up @@ -541,5 +547,8 @@ def update_apps(app_name):
except Exception as e:
import traceback

print(traceback.format_exc())
click.echo(f"An error occurred: {e}")
error_message = click.style(
f"An error occurred while updating app {tenant}: {traceback.format_exc()}",
fg="red",
bold=True,
)
Loading