You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jumpserver/apps/terminal/models/virtualapp/virtualapp.py

104 lines
3.9 KiB

import os
import shutil
from django.conf import settings
from django.core.files.storage import default_storage
from django.db import models
from django.utils._os import safe_join
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import ValidationError
from common.db.models import JMSBaseModel
from common.utils import lazyproperty
from common.utils.yml import yaml_load_with_i18n
__all__ = ['VirtualApp', 'VirtualAppPublication']
class VirtualApp(JMSBaseModel):
name = models.SlugField(max_length=128, verbose_name=_('Name'), unique=True)
display_name = models.CharField(max_length=128, verbose_name=_('Display name'))
version = models.CharField(max_length=16, verbose_name=_('Version'))
author = models.CharField(max_length=128, verbose_name=_('Author'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
image_name = models.CharField(max_length=128, verbose_name=_('Image name'))
image_protocol = models.CharField(max_length=16, default='vnc', verbose_name=_('Image protocol'))
image_port = models.IntegerField(default=5900, verbose_name=_('Image port'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
tags = models.JSONField(default=list, verbose_name=_('Tags'))
providers = models.ManyToManyField(
through_fields=('app', 'provider',), through='VirtualAppPublication',
to='AppProvider', verbose_name=_('Providers')
)
class Meta:
verbose_name = _('Virtual app')
def __str__(self):
return self.name
@property
def path(self):
return default_storage.path('virtual_apps/{}'.format(self.name))
@lazyproperty
def readme(self):
readme_file = os.path.join(self.path, 'README.md')
if os.path.isfile(readme_file):
with open(readme_file, 'r') as f:
return f.read()
return ''
@property
def icon(self):
path = os.path.join(self.path, 'icon.png')
if not os.path.exists(path):
return None
return os.path.join(settings.MEDIA_URL, 'virtual_apps', self.name, 'icon.png')
@staticmethod
def validate_pkg(d):
files = ['manifest.yml', 'icon.png', ]
for name in files:
path = safe_join(d, name)
if not os.path.exists(path):
raise ValidationError({'error': _('Applet pkg not valid, Missing file {}').format(name)})
with open(safe_join(d, 'manifest.yml'), encoding='utf8') as f:
manifest = yaml_load_with_i18n(f)
if not manifest.get('name', ''):
raise ValidationError({'error': 'Missing name in manifest.yml'})
return manifest
@classmethod
def install_from_dir(cls, path):
from terminal.serializers import VirtualAppSerializer
manifest = cls.validate_pkg(path)
name = manifest['name']
instance = cls.objects.filter(name=name).first()
serializer = VirtualAppSerializer(instance=instance, data=manifest)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
pkg_path = default_storage.path('virtual_apps/{}'.format(name))
if os.path.exists(pkg_path):
shutil.rmtree(pkg_path)
shutil.copytree(path, pkg_path)
return instance, serializer
class VirtualAppPublication(JMSBaseModel):
provider = models.ForeignKey(
'AppProvider', on_delete=models.CASCADE, related_name='publications', verbose_name=_('App Provider')
)
app = models.ForeignKey(
12 months ago
'VirtualApp', on_delete=models.CASCADE, related_name='publications', verbose_name=_('Virtual app')
)
status = models.CharField(max_length=16, default='pending', verbose_name=_('Status'))
class Meta:
verbose_name = _('Virtual app publication')
unique_together = ('provider', 'app')