Merge branch 'feat/self_hosted_instance' of github.com:makeplane/plane into feat/self_hosted_instance

This commit is contained in:
sriram veeraghanta 2023-11-16 12:35:23 +05:30
commit c4d5ccdae4
9 changed files with 185 additions and 148 deletions

View File

@ -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()

View File

@ -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

View File

@ -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}"))

View File

@ -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"))

View File

@ -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()

View File

@ -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',),
},
),

View File

@ -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',),
},
),
]