diff --git a/apiserver/bin/instance_configuration.py b/apiserver/bin/instance_configuration.py deleted file mode 100644 index b30875057..000000000 --- a/apiserver/bin/instance_configuration.py +++ /dev/null @@ -1,49 +0,0 @@ -import os, sys - - -sys.path.append("/code") - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production") - -import django - -django.setup() - - -def load_config(): - from plane.license.models import InstanceConfiguration - - config_keys = { - # Authentication Settings - "GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"), - "GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"), - "GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"), - "ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"), - "ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"), - "ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"), - # Email Settings - "EMAIL_HOST": os.environ.get("EMAIL_HOST", ""), - "EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""), - "EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"), - "EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"), - "EMAIL_FROM": os.environ.get("EMAIL_FROM", ""), - "EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"), - "EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"), - # Open AI Settings - "OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"), - "OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"), - "GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"), - } - - for key, value in config_keys.items(): - obj, created = InstanceConfiguration.objects.get_or_create( - key=key - ) - obj.value = value - obj.save() - - print(f"{key} loaded with value from environment variable.") - - -if __name__ == "__main__": - load_config() diff --git a/apiserver/bin/takeoff b/apiserver/bin/takeoff index 92834b8c1..44f251155 100755 --- a/apiserver/bin/takeoff +++ b/apiserver/bin/takeoff @@ -4,9 +4,9 @@ python manage.py wait_for_db python manage.py migrate # Register instance -python bin/instance_registration.py +python manage.py register_instance # Load the configuration variable -python bin/instance_configuration.py +python manage.py configure_instance # Create the default bucket python bin/bucket_script.py diff --git a/apiserver/plane/db/management/commands/create_bucket.py b/apiserver/plane/db/management/commands/create_bucket.py new file mode 100644 index 000000000..054523bf9 --- /dev/null +++ b/apiserver/plane/db/management/commands/create_bucket.py @@ -0,0 +1,71 @@ +# Python imports +import boto3 +import json +from botocore.exceptions import ClientError + +# Django imports +from django.core.management import BaseCommand +from django.conf import settings + +class Command(BaseCommand): + help = "Create the default bucket for the instance" + + def set_bucket_public_policy(self, s3_client, bucket_name): + public_policy = { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": "*", + "Action": ["s3:GetObject"], + "Resource": [f"arn:aws:s3:::{bucket_name}/*"] + }] + } + + try: + s3_client.put_bucket_policy( + Bucket=bucket_name, + Policy=json.dumps(public_policy) + ) + self.stdout.write(self.style.SUCCESS(f"Public read access policy set for bucket '{bucket_name}'.")) + except ClientError as e: + self.stdout.write(self.style.ERROR(f"Error setting public read access policy: {e}")) + + + def handle(self, *args, **options): + # Create a session using the credentials from Django settings + try: + session = boto3.session.Session( + aws_access_key_id=settings.AWS_ACCESS_KEY_ID, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + ) + # Create an S3 client using the session + s3_client = session.client('s3', endpoint_url=settings.AWS_S3_ENDPOINT_URL) + bucket_name = settings.AWS_STORAGE_BUCKET_NAME + + self.stdout.write(self.style.NOTICE("Checking bucket...")) + + # Check if the bucket exists + s3_client.head_bucket(Bucket=bucket_name) + + self.set_bucket_public_policy(s3_client, bucket_name) + except ClientError as e: + error_code = int(e.response['Error']['Code']) + bucket_name = settings.AWS_STORAGE_BUCKET_NAME + if error_code == 404: + # Bucket does not exist, create it + self.stdout.write(self.style.WARNING(f"Bucket '{bucket_name}' does not exist. Creating bucket...")) + try: + s3_client.create_bucket(Bucket=bucket_name) + self.stdout.write(self.style.SUCCESS(f"Bucket '{bucket_name}' created successfully.")) + self.set_bucket_public_policy(s3_client, bucket_name) + except ClientError as create_error: + self.stdout.write(self.style.ERROR(f"Failed to create bucket: {create_error}")) + elif error_code == 403: + # Access to the bucket is forbidden + self.stdout.write(self.style.ERROR(f"Access to the bucket '{bucket_name}' is forbidden. Check permissions.")) + else: + # Another ClientError occurred + self.stdout.write(self.style.ERROR(f"Failed to check bucket: {e}")) + except Exception as ex: + # Handle any other exception + self.stdout.write(self.style.ERROR(f"An error occurred: {ex}")) \ No newline at end of file diff --git a/apiserver/plane/license/management/__init__.py b/apiserver/plane/license/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apiserver/plane/license/management/commands/__init__.py b/apiserver/plane/license/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apiserver/plane/license/management/commands/configure_instance.py b/apiserver/plane/license/management/commands/configure_instance.py new file mode 100644 index 000000000..d71d9f590 --- /dev/null +++ b/apiserver/plane/license/management/commands/configure_instance.py @@ -0,0 +1,46 @@ +# Python imports +import os + +# Django imports +from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone + +# Module imports +from plane.license.models import InstanceConfiguration + +class Command(BaseCommand): + help = "Configure instance variables" + + def handle(self, *args, **options): + config_keys = { + # Authentication Settings + "GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"), + "GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"), + "GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"), + "ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"), + "ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"), + "ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"), + # Email Settings + "EMAIL_HOST": os.environ.get("EMAIL_HOST", ""), + "EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""), + "EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"), + "EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"), + "EMAIL_FROM": os.environ.get("EMAIL_FROM", ""), + "EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"), + "EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"), + # Open AI Settings + "OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"), + "OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"), + "GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"), + } + + for key, value in config_keys.items(): + obj, created = InstanceConfiguration.objects.get_or_create( + key=key + ) + if created: + obj.value = value + obj.save() + self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) + else: + self.stdout.write(self.style.WARNING(f"{key} configuration already exists")) \ No newline at end of file diff --git a/apiserver/bin/instance_registration.py b/apiserver/plane/license/management/commands/register_instance.py similarity index 65% rename from apiserver/bin/instance_registration.py rename to apiserver/plane/license/management/commands/register_instance.py index e76ee835c..855a3a035 100644 --- a/apiserver/bin/instance_registration.py +++ b/apiserver/plane/license/management/commands/register_instance.py @@ -1,41 +1,43 @@ # Python imports -import os, sys import json -import uuid +import os import requests +import uuid # Django imports +from django.core.management.base import BaseCommand, CommandError from django.utils import timezone +from django.core.exceptions import ValidationError +from django.core.validators import validate_email + +# Module imports +from plane.db.models import User +from plane.license.models import Instance, InstanceAdmin -sys.path.append("/code") - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production") - -import django - -django.setup() - - -def instance_registration(): - try: - # Module imports - from plane.db.models import User - from plane.license.models import Instance, InstanceAdmin +class Command(BaseCommand): + help = "Check if instance in registered else register" + def handle(self, *args, **options): # Check if the instance is registered instance = Instance.objects.first() # If instance is None then register this instance if instance is None: - with open("/code/package.json", "r") as file: + with open("package.json", "r") as file: # Load JSON content from the file data = json.load(file) admin_email = os.environ.get("ADMIN_EMAIL") + + try: + validate_email(admin_email) + except ValidationError: + CommandError(f"{admin_email} is not a valid ADMIN_EMAIL") + # Raise an exception if the admin email is not provided if not admin_email: - raise Exception("ADMIN_EMAIL is required") + raise CommandError("ADMIN_EMAIL is required") # Check if the admin email user exists user = User.objects.filter(email=admin_email).first() @@ -49,7 +51,7 @@ def instance_registration(): license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL") if not license_engine_base_url: - raise Exception("LICENSE_ENGINE_BASE_URL is required") + raise CommandError("LICENSE_ENGINE_BASE_URL is required") headers = {"Content-Type": "application/json"} @@ -84,21 +86,19 @@ def instance_registration(): role=20, ) - print( - f"Instance succesfully registered with owner: {instance.primary_owner.email}" + self.stdout.write( + self.style.SUCCESS( + f"Instance succesfully registered with owner: {instance.primary_owner.email}" + ) ) return - print("Instance could not be registered") + self.stdout.write(self.style.WARNING("Instance could not be registered")) return else: - print( - f"Instance already registered with instance owner: {instance.primary_owner.email}" + self.stdout.write( + self.style.SUCCESS( + f"Instance already registered with instance owner: {instance.primary_owner.email}" + ) ) return - except ImportError: - raise ImportError - - -if __name__ == "__main__": - instance_registration() diff --git a/apiserver/plane/license/migrations/0001_initial.py b/apiserver/plane/license/migrations/0001_initial.py index 6e9d63eb9..db620a18e 100644 --- a/apiserver/plane/license/migrations/0001_initial.py +++ b/apiserver/plane/license/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-11-13 14:31 +# Generated by Django 4.2.5 on 2023-11-15 14:22 from django.conf import settings from django.db import migrations, models @@ -15,6 +15,34 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Instance', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('instance_name', models.CharField(max_length=255)), + ('whitelist_emails', models.TextField(blank=True, null=True)), + ('instance_id', models.CharField(max_length=25, unique=True)), + ('license_key', models.CharField(blank=True, max_length=256, null=True)), + ('api_key', models.CharField(max_length=16)), + ('version', models.CharField(max_length=10)), + ('primary_email', models.CharField(max_length=256)), + ('last_checked_at', models.DateTimeField()), + ('namespace', models.CharField(blank=True, max_length=50, null=True)), + ('is_telemetry_enabled', models.BooleanField(default=True)), + ('is_support_required', models.BooleanField(default=True)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('primary_owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL)), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ], + options={ + 'verbose_name': 'Instance', + 'verbose_name_plural': 'Instances', + 'db_table': 'instances', + 'ordering': ('-created_at',), + }, + ), migrations.CreateModel( name='InstanceConfiguration', fields=[ @@ -34,30 +62,21 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='Instance', + name='InstanceAdmin', fields=[ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('instance_name', models.CharField(max_length=255)), - ('whitelist_emails', models.TextField(blank=True, null=True)), - ('instance_id', models.CharField(max_length=25, unique=True)), - ('license_key', models.CharField(blank=True, max_length=256, null=True)), - ('api_key', models.CharField(max_length=16)), - ('version', models.CharField(max_length=10)), - ('email', models.CharField(max_length=256)), - ('last_checked_at', models.DateTimeField()), - ('namespace', models.CharField(blank=True, max_length=50, null=True)), - ('is_telemetry_enabled', models.BooleanField(default=True)), - ('is_support_required', models.BooleanField(default=True)), + ('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)), ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), - ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)), + ('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')), ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'Instance', - 'verbose_name_plural': 'Instances', - 'db_table': 'instances', + 'verbose_name': 'Instance Admin', + 'verbose_name_plural': 'Instance Admins', + 'db_table': 'instance_admins', 'ordering': ('-created_at',), }, ), diff --git a/apiserver/plane/license/migrations/0002_rename_email_instance_primary_email_and_more.py b/apiserver/plane/license/migrations/0002_rename_email_instance_primary_email_and_more.py deleted file mode 100644 index 7589239b7..000000000 --- a/apiserver/plane/license/migrations/0002_rename_email_instance_primary_email_and_more.py +++ /dev/null @@ -1,50 +0,0 @@ -# Generated by Django 4.2.5 on 2023-11-14 10:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('license', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='instance', - old_name='email', - new_name='primary_email', - ), - migrations.RemoveField( - model_name='instance', - name='owner', - ), - migrations.AddField( - model_name='instance', - name='primary_owner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL), - ), - migrations.CreateModel( - name='InstanceAdmin', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), - ('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')), - ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), - ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Instance Admin', - 'verbose_name_plural': 'Instance Admins', - 'db_table': 'instance_admins', - 'ordering': ('-created_at',), - }, - ), - ]