dev: instance configuration on instance bootup

This commit is contained in:
pablohashescobar 2023-11-13 20:29:09 +05:30
parent 216f07f9d8
commit 4fdd6a0e26
8 changed files with 142 additions and 189 deletions

View File

@ -0,0 +1,80 @@
# Python imports
import os, sys
import json
import uuid
import requests
# Django imports
from django.utils import timezone
# Module imports
from plane.db.models import User
from plane.license.models import Instance
sys.path.append("/code")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
import django
django.setup()
def instance_registration():
# Check if the instance is registered
instance = Instance.objects.first()
# If instance is None then register this instance
if instance is None:
with open("package.json", "r") as file:
# Load JSON content from the file
data = json.load(file)
admin_email = os.environ.get("ADMIN_EMAIL")
# Raise an exception if the admin email is not provided
if not admin_email:
raise Exception("ADMIN_EMAIL is required")
# Check if the admin email user exists
user = User.objects.filter(email=admin_email).first()
# If the user does not exist create the user and add him to the database
if user is None:
user = User.objects.create(email=admin_email, username=uuid.uuid4().hex)
user.set_password(uuid.uuid4().hex)
user.save()
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")
headers = {"Content-Type": "application/json"}
payload = {
"email": user.email,
"version": data.get("version", 0.1),
}
response = requests.post(
f"{license_engine_base_url}/api/instances",
headers=headers,
data=json.dumps(payload),
)
if response.status_code == 201:
data = response.json()
instance = Instance.objects.create(
instance_name="Plane Free",
instance_id=data.get("id"),
license_key=data.get("license_key"),
api_key=data.get("api_key"),
version=data.get("version"),
email=data.get("email"),
owner=user,
last_checked_at=timezone.now(),
)
print("Instance succesfully registered")
print("Instance could not be registered")
else:
print(f"Instance already registered with instance owner: {instance.owner.email}")

View File

@ -3,6 +3,9 @@ set -e
python manage.py wait_for_db
python manage.py migrate
# Register instance
python bin/instance_registration.py
# Load the configuration variable
python bin/instance_configuration.py
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -

View File

@ -10,7 +10,6 @@ from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
# Module imports
from plane.api.views import BaseAPIView
@ -20,122 +19,29 @@ from plane.db.models import User
class InstanceEndpoint(BaseAPIView):
def get_permissions(self):
if self.request.method == "PATCH":
self.permission_classes = [
IsAuthenticated,
]
else:
self.permission_classes = [
AllowAny,
]
return super(InstanceEndpoint, self).get_permissions()
def post(self, request):
email = request.data.get("email", False)
password = request.data.get("password", False)
with open("package.json", "r") as file:
# Load JSON content from the file
data = json.load(file)
if not email or not password:
return Response(
{"error": "Email and Password are required"},
status=status.HTTP_400_BAD_REQUEST,
)
instance = Instance.objects.first()
if instance is None:
# Register the instance
user = User.objects.filter(email=email).first()
if user is None:
user = User.objects.create(
email=email,
username=uuid.uuid4().hex,
)
user.set_password(password)
user.save()
else:
if not user.check_password(password):
return Response(
{
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_401_UNAUTHORIZED,
)
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
headers = {"Content-Type": "application/json"}
payload = {
"email": email,
"version": data.get("version", 0.1),
"domain": str(request.headers.get("Host")),
}
response = requests.post(
f"{LICENSE_ENGINE_BASE_URL}/api/instances",
headers=headers,
data=json.dumps(payload),
)
if response.status_code == 201:
data = response.json()
instance = Instance.objects.create(
instance_id=data.get("id"),
license_key=data.get("license_key"),
api_key=data.get("api_key"),
version=data.get("version"),
email=data.get("email"),
user=user,
last_checked_at=timezone.now(),
)
serializer = InstanceSerializer(instance)
return Response(
{
"data": serializer.data,
"message": "Instance registered succesfully",
},
status=status.HTTP_200_OK,
)
return Response(
{"error": "Unable to create instance"}, status=response.status_code
)
serializer = InstanceSerializer(instance)
return Response(
{
"message": "Instance is already registered",
"data": serializer.data,
},
status=status.HTTP_200_OK,
)
def get(self, request):
instance = Instance.objects.first()
# get the instance
if instance is None:
return Response({"activated": False}, status=status.HTTP_400_BAD_REQUEST)
# Check the accessing user
if str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
# Return instance
serializer = InstanceSerializer(instance)
data = {
"data": serializer.data,
"activated": True,
}
return Response(data, status=status.HTTP_200_OK)
def patch(self, request):
# Get the instance
instance = Instance.objects.first()
# Check the accessing user
if instance is not None and str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
serializer = InstanceSerializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
@ -144,27 +50,32 @@ class InstanceEndpoint(BaseAPIView):
class TransferOwnerEndpoint(BaseAPIView):
permission_classes = [
IsAuthenticated,
]
# Transfer the owner of the instance
def post(self, request):
instance = Instance.objects.first()
if str(instance.user_id) != str(request.user.id):
# Check the accessing user
if instance is not None and str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
user_id = request.data.get("user_id", False)
if not user_id:
# Get the email of the new user
email = request.data.get("email", False)
if not email:
return Response(
{"error": "User is required"}, status=status.HTTP_400_BAD_REQUEST
)
user = User.objects.get(pk=user_id)
# Get users
user = User.objects.get(email=email)
user.is_superuser = True
user.save(update_fields=["is_superuser"])
# Save the instance user
instance.owner = user
instance.email = user.email
instance.save(update_fields=["owner", "email"])
instance.user = user
instance.save()
return Response(
{"message": "Owner successfully updated"}, status=status.HTTP_200_OK
)

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-11-08 06:49
# Generated by Django 4.2.5 on 2023-11-13 14:31
from django.conf import settings
from django.db import migrations, models
@ -15,23 +15,44 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='InstanceConfiguration',
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)),
('key', models.CharField(max_length=100, unique=True)),
('value', models.TextField(blank=True, default=None, null=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')),
('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 Configuration',
'verbose_name_plural': 'Instance Configurations',
'db_table': 'instance_configurations',
'ordering': ('-created_at',),
},
),
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(max_length=256)),
('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)),
('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)),
('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',

View File

@ -1,34 +0,0 @@
# Generated by Django 4.2.5 on 2023-11-08 11:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('license', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='InstanceConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('key', models.CharField(max_length=100, unique=True)),
('value', models.TextField()),
('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')),
('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 Configuration',
'verbose_name_plural': 'Instance Configurations',
'db_table': 'instance_configurations',
'ordering': ('-created_at',),
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.5 on 2023-11-08 13:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('license', '0002_instanceconfiguration'),
]
operations = [
migrations.AlterField(
model_name='instanceconfiguration',
name='value',
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.5 on 2023-11-08 14:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('license', '0003_alter_instanceconfiguration_value'),
]
operations = [
migrations.AlterField(
model_name='instanceconfiguration',
name='value',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -7,18 +7,25 @@ from plane.db.models import BaseModel
from plane.db.mixins import AuditModel
class Instance(BaseModel):
# General informations
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(max_length=256)
license_key = models.CharField(max_length=256, null=True, blank=True)
api_key = models.CharField(max_length=16)
version = models.CharField(max_length=10)
# User information
email = models.CharField(max_length=256)
user = models.ForeignKey(
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="instance_owner",
)
# Instnace specifics
last_checked_at = models.DateTimeField()
namespace = models.CharField(max_length=50, blank=True, null=True)
# telemetry and support
is_telemetry_enabled = models.BooleanField(default=True)
is_support_required = models.BooleanField(default=True)
@ -30,7 +37,8 @@ class Instance(BaseModel):
class InstanceConfiguration(AuditModel):
class InstanceConfiguration(BaseModel):
# The instance configuration variables
key = models.CharField(max_length=100, unique=True)
value = models.TextField(null=True, blank=True, default=None)