mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
						commit
						10f4a0d67e
					
				| 
						 | 
				
			
			@ -34,7 +34,7 @@ class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet):
 | 
			
		|||
    date_range_filter_fields = [
 | 
			
		||||
        ('date_start', ('date_from', 'date_to'))
 | 
			
		||||
    ]
 | 
			
		||||
    filterset_fields = ['user', 'asset', 'system_user', 'filename']
 | 
			
		||||
    filterset_fields = ['user', 'asset', 'account', 'filename']
 | 
			
		||||
    search_fields = filterset_fields
 | 
			
		||||
    ordering = ['-date_start']
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -343,9 +343,9 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS:
 | 
			
		|||
        }
 | 
			
		||||
    })
 | 
			
		||||
    if REDIS_USE_SSL:
 | 
			
		||||
        REDIS_OPTIONS['CONNECTION_POOL_KWARGS'].update({
 | 
			
		||||
            'connection_class': SentinelManagedSSLConnection
 | 
			
		||||
        })
 | 
			
		||||
        CONNECTION_POOL_KWARGS = REDIS_OPTIONS['CONNECTION_POOL_KWARGS']
 | 
			
		||||
        CONNECTION_POOL_KWARGS['connection_class'] = SentinelManagedSSLConnection
 | 
			
		||||
        REDIS_OPTIONS['CONNECTION_POOL_KWARGS'] = CONNECTION_POOL_KWARGS
 | 
			
		||||
    DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory'
 | 
			
		||||
else:
 | 
			
		||||
    REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ from django.shortcuts import get_object_or_404
 | 
			
		|||
from rest_framework.response import Response
 | 
			
		||||
 | 
			
		||||
from ops.models import Job, JobExecution
 | 
			
		||||
from ops.models.job import JobAuditLog
 | 
			
		||||
from ops.serializers.job import JobSerializer, JobExecutionSerializer
 | 
			
		||||
 | 
			
		||||
__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail', ]
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +57,8 @@ class JobExecutionViewSet(OrgBulkModelViewSet):
 | 
			
		|||
 | 
			
		||||
    def perform_create(self, serializer):
 | 
			
		||||
        instance = serializer.save()
 | 
			
		||||
        instance.job_version = instance.job.version
 | 
			
		||||
        instance.save()
 | 
			
		||||
        task = run_ops_job_execution.delay(instance.id)
 | 
			
		||||
        set_task_to_serializer_data(serializer, task)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
# Generated by Django 3.2.14 on 2022-12-20 11:41
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import simple_history.models
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('ops', '0029_auto_20221215_1712'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='JobAuditLog',
 | 
			
		||||
            fields=[
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'proxy': True,
 | 
			
		||||
                'indexes': [],
 | 
			
		||||
                'constraints': [],
 | 
			
		||||
            },
 | 
			
		||||
            bases=('ops.jobexecution',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='job',
 | 
			
		||||
            name='version',
 | 
			
		||||
            field=models.IntegerField(default=0),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='jobexecution',
 | 
			
		||||
            name='job_version',
 | 
			
		||||
            field=models.IntegerField(default=0),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='HistoricalJob',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
 | 
			
		||||
                ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
 | 
			
		||||
                ('date_created', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Date created')),
 | 
			
		||||
                ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
 | 
			
		||||
                ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
 | 
			
		||||
                ('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
 | 
			
		||||
                ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
 | 
			
		||||
                ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
 | 
			
		||||
                ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
 | 
			
		||||
                ('name', models.CharField(max_length=128, null=True, verbose_name='Name')),
 | 
			
		||||
                ('instant', models.BooleanField(default=False)),
 | 
			
		||||
                ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')),
 | 
			
		||||
                ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')),
 | 
			
		||||
                ('chdir', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir')),
 | 
			
		||||
                ('timeout', models.IntegerField(default=60, verbose_name='Timeout (Seconds)')),
 | 
			
		||||
                ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')),
 | 
			
		||||
                ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')),
 | 
			
		||||
                ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')),
 | 
			
		||||
                ('use_parameter_define', models.BooleanField(default=False, verbose_name='Use Parameter Define')),
 | 
			
		||||
                ('parameters_define', models.JSONField(default=dict, verbose_name='Parameters define')),
 | 
			
		||||
                ('comment', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment')),
 | 
			
		||||
                ('version', models.IntegerField(default=0)),
 | 
			
		||||
                ('history_id', models.AutoField(primary_key=True, serialize=False)),
 | 
			
		||||
                ('history_date', models.DateTimeField(db_index=True)),
 | 
			
		||||
                ('history_change_reason', models.CharField(max_length=100, null=True)),
 | 
			
		||||
                ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
 | 
			
		||||
                ('creator', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
 | 
			
		||||
                ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                ('playbook', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ops.playbook', verbose_name='Playbook')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'historical job',
 | 
			
		||||
                'verbose_name_plural': 'historical jobs',
 | 
			
		||||
                'ordering': ('-history_date', '-history_id'),
 | 
			
		||||
                'get_latest_by': ('history_date', 'history_id'),
 | 
			
		||||
            },
 | 
			
		||||
            bases=(simple_history.models.HistoricalChanges, models.Model),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +11,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		|||
 | 
			
		||||
__all__ = ["Job", "JobExecution", "JobAuditLog"]
 | 
			
		||||
 | 
			
		||||
from simple_history.models import HistoricalRecords
 | 
			
		||||
 | 
			
		||||
from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner
 | 
			
		||||
from ops.mixin import PeriodTaskModelMixin
 | 
			
		||||
from ops.variables import *
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,11 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
 | 
			
		|||
    use_parameter_define = models.BooleanField(default=False, verbose_name=(_('Use Parameter Define')))
 | 
			
		||||
    parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define'))
 | 
			
		||||
    comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
 | 
			
		||||
    version = models.IntegerField(default=0)
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    def get_history(self, version):
 | 
			
		||||
        return self.history.filter(version=version).first()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def last_execution(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +85,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
 | 
			
		|||
        return JMSInventory(self.assets.all(), self.runas_policy, self.runas)
 | 
			
		||||
 | 
			
		||||
    def create_execution(self):
 | 
			
		||||
        return self.executions.create()
 | 
			
		||||
        return self.executions.create(job_version=self.version)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        ordering = ['date_created']
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +96,7 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
    task_id = models.UUIDField(null=True)
 | 
			
		||||
    status = models.CharField(max_length=16, verbose_name=_('Status'), default=JobStatus.running)
 | 
			
		||||
    job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True)
 | 
			
		||||
    job_version = models.IntegerField(default=0)
 | 
			
		||||
    parameters = models.JSONField(default=dict, verbose_name=_('Parameters'))
 | 
			
		||||
    result = models.JSONField(blank=True, null=True, verbose_name=_('Result'))
 | 
			
		||||
    summary = models.JSONField(default=dict, verbose_name=_('Summary'))
 | 
			
		||||
| 
						 | 
				
			
			@ -97,12 +105,18 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
    date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
 | 
			
		||||
    date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def current_job(self):
 | 
			
		||||
        if self.job.version != self.job_version:
 | 
			
		||||
            return self.job.get_history(self.job_version)
 | 
			
		||||
        return self.job
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def material(self):
 | 
			
		||||
        if self.job.type == 'adhoc':
 | 
			
		||||
            return "{}:{}".format(self.job.module, self.job.args)
 | 
			
		||||
        if self.job.type == 'playbook':
 | 
			
		||||
            return "{}:{}:{}".format(self.org.name, self.job.creator.name, self.job.playbook.name)
 | 
			
		||||
        if self.current_job.type == 'adhoc':
 | 
			
		||||
            return "{}:{}".format(self.current_job.module, self.current_job.args)
 | 
			
		||||
        if self.current_job.type == 'playbook':
 | 
			
		||||
            return "{}:{}:{}".format(self.org.name, self.current_job.creator.name, self.current_job.playbook.name)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def assent_result_detail(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +125,7 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
                "summary": self.count,
 | 
			
		||||
                "detail": [],
 | 
			
		||||
            }
 | 
			
		||||
            for asset in self.job.assets.all():
 | 
			
		||||
            for asset in self.current_job.assets.all():
 | 
			
		||||
                asset_detail = {
 | 
			
		||||
                    "name": asset.name,
 | 
			
		||||
                    "status": "ok",
 | 
			
		||||
| 
						 | 
				
			
			@ -144,17 +158,17 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
 | 
			
		||||
    @property
 | 
			
		||||
    def job_type(self):
 | 
			
		||||
        return self.job.type
 | 
			
		||||
        return self.current_job.type
 | 
			
		||||
 | 
			
		||||
    def compile_shell(self):
 | 
			
		||||
        if self.job.type != 'adhoc':
 | 
			
		||||
        if self.current_job.type != 'adhoc':
 | 
			
		||||
            return
 | 
			
		||||
        result = "{}{}{} ".format('\'', self.job.args, '\'')
 | 
			
		||||
        result += "chdir={}".format(self.job.chdir)
 | 
			
		||||
        result = "{}{}{} ".format('\'', self.current_job.args, '\'')
 | 
			
		||||
        result += "chdir={}".format(self.current_job.chdir)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def get_runner(self):
 | 
			
		||||
        inv = self.job.inventory
 | 
			
		||||
        inv = self.current_job.inventory
 | 
			
		||||
        inv.write_to_file(self.inventory_path)
 | 
			
		||||
        self.summary = self.result = {"excludes": {}}
 | 
			
		||||
        if len(inv.exclude_hosts) > 0:
 | 
			
		||||
| 
						 | 
				
			
			@ -170,15 +184,15 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
        static_variables = self.gather_static_variables()
 | 
			
		||||
        extra_vars.update(static_variables)
 | 
			
		||||
 | 
			
		||||
        if self.job.type == 'adhoc':
 | 
			
		||||
        if self.current_job.type == 'adhoc':
 | 
			
		||||
            args = self.compile_shell()
 | 
			
		||||
            runner = AdHocRunner(
 | 
			
		||||
                self.inventory_path, self.job.module, module_args=args,
 | 
			
		||||
                self.inventory_path, self.current_job.module, module_args=args,
 | 
			
		||||
                pattern="all", project_dir=self.private_dir, extra_vars=extra_vars,
 | 
			
		||||
            )
 | 
			
		||||
        elif self.job.type == 'playbook':
 | 
			
		||||
        elif self.current_job.type == 'playbook':
 | 
			
		||||
            runner = PlaybookRunner(
 | 
			
		||||
                self.inventory_path, self.job.playbook.entry
 | 
			
		||||
                self.inventory_path, self.current_job.playbook.entry
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception("unsupported job type")
 | 
			
		||||
| 
						 | 
				
			
			@ -186,8 +200,8 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
 | 
			
		||||
    def gather_static_variables(self):
 | 
			
		||||
        default = {
 | 
			
		||||
            JMS_JOB_ID: str(self.job.id),
 | 
			
		||||
            JMS_JOB_NAME: self.job.name,
 | 
			
		||||
            JMS_JOB_ID: str(self.current_job.id),
 | 
			
		||||
            JMS_JOB_NAME: self.current_job.name,
 | 
			
		||||
        }
 | 
			
		||||
        if self.creator:
 | 
			
		||||
            default.update({JMS_USERNAME: self.creator.username})
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +238,7 @@ class JobExecution(JMSOrgBaseModel):
 | 
			
		|||
    @property
 | 
			
		||||
    def private_dir(self):
 | 
			
		||||
        uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id
 | 
			
		||||
        job_name = self.job.name if self.job.name else 'instant'
 | 
			
		||||
        job_name = self.current_job.name if self.current_job.name else 'instant'
 | 
			
		||||
        return os.path.join(settings.ANSIBLE_DIR, job_name, uniq)
 | 
			
		||||
 | 
			
		||||
    def set_error(self, error):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ from celery import signals
 | 
			
		|||
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.db.models.signals import pre_save
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from django.db.utils import ProgrammingError
 | 
			
		||||
from django.utils import translation, timezone
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +13,7 @@ from common.signals import django_ready
 | 
			
		|||
from common.db.utils import close_old_connections, get_logger
 | 
			
		||||
 | 
			
		||||
from .celery import app
 | 
			
		||||
from .models import CeleryTaskExecution, CeleryTask
 | 
			
		||||
from .models import CeleryTaskExecution, CeleryTask, Job
 | 
			
		||||
 | 
			
		||||
logger = get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,12 @@ TASK_LANG_CACHE_KEY = 'TASK_LANG_{}'
 | 
			
		|||
TASK_LANG_CACHE_TTL = 1800
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(pre_save, sender=Job)
 | 
			
		||||
def on_account_pre_create(sender, instance, **kwargs):
 | 
			
		||||
    # 升级版本号
 | 
			
		||||
    instance.version += 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(django_ready)
 | 
			
		||||
def sync_registered_tasks(*args, **kwargs):
 | 
			
		||||
    with transaction.atomic():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,15 +13,15 @@ class PermissionBaseFilter(BaseFilterSet):
 | 
			
		|||
    is_valid = filters.BooleanFilter(method='do_nothing')
 | 
			
		||||
    user_id = filters.UUIDFilter(method='do_nothing')
 | 
			
		||||
    username = filters.CharFilter(method='do_nothing')
 | 
			
		||||
    system_user_id = filters.UUIDFilter(method='do_nothing')
 | 
			
		||||
    system_user = filters.CharFilter(method='do_nothing')
 | 
			
		||||
    account_id = filters.UUIDFilter(method='do_nothing')
 | 
			
		||||
    account = filters.CharFilter(method='do_nothing')
 | 
			
		||||
    user_group_id = filters.UUIDFilter(method='do_nothing')
 | 
			
		||||
    user_group = filters.CharFilter(method='do_nothing')
 | 
			
		||||
    all = filters.BooleanFilter(method='do_nothing')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        fields = (
 | 
			
		||||
            'user_id', 'username', 'system_user_id', 'system_user',
 | 
			
		||||
            'user_id', 'username', 'account_id', 'account',
 | 
			
		||||
            'user_group_id', 'user_group', 'name', 'all', 'is_valid',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ class WebMethod(TextChoices):
 | 
			
		|||
            Protocol.redis: [cls.web_cli],
 | 
			
		||||
            Protocol.mongodb: [cls.web_cli],
 | 
			
		||||
 | 
			
		||||
            Protocol.k8s: [cls.web_gui],
 | 
			
		||||
            Protocol.k8s: [cls.web_cli],
 | 
			
		||||
            Protocol.http: []
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue