feat: Supports running adhoc,playbook with variable (#14417)

* perf:Create a job that supports adding node parameters

* feat: add variable model

* feat: Modify Variable and AdHoc models,

* feat: Parameters can be set when running job

* feat: Supports setting  variable type

* feat: Supports running adhoc with parameters

* feat: Supports running playbook with parameters

* fix: Translate

* feat: Support setting variables for scheduled tasks

* perf: Translate

---------

Co-authored-by: wangruidong <940853815@qq.com>
This commit is contained in:
fit2bot
2024-11-07 10:38:34 +08:00
committed by GitHub
parent 8f11167db0
commit c96ae1022b
30 changed files with 565 additions and 141 deletions

View File

@@ -78,7 +78,7 @@ class AdHocRunner:
class PlaybookRunner:
def __init__(self, inventory, playbook, project_dir='/tmp/', callback=None):
def __init__(self, inventory, playbook, project_dir='/tmp/', callback=None, extra_vars=None, ):
self.id = uuid.uuid4()
self.inventory = inventory
@@ -89,6 +89,9 @@ class PlaybookRunner:
self.cb = callback
self.isolate = True
self.envs = {}
if extra_vars is None:
extra_vars = {}
self.extra_vars = extra_vars
def copy_playbook(self):
entry = os.path.basename(self.playbook)
@@ -119,6 +122,7 @@ class PlaybookRunner:
status_handler=self.cb.status_handler,
host_cwd=self.project_dir,
envvars=self.envs,
extravars=self.extra_vars,
**kwargs
)
return self.cb

View File

@@ -4,3 +4,4 @@ from .adhoc import *
from .celery import *
from .job import *
from .playbook import *
from .variable import *

View File

@@ -22,6 +22,7 @@ from ops.models import Job, JobExecution
from ops.serializers.job import (
JobSerializer, JobExecutionSerializer, FileSerializer, JobTaskStopSerializer
)
from ops.utils import merge_nodes_and_assets
__all__ = [
'JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobExecutionTaskDetail', 'UsernameHintsAPI'
@@ -36,8 +37,6 @@ from accounts.models import Account
from assets.const import Protocol
from perms.const import ActionChoices
from perms.utils.asset_perm import PermAssetDetailUtil
from perms.models import PermNode
from perms.utils import UserPermAssetUtil
from jumpserver.settings import get_file_md5
@@ -47,26 +46,12 @@ def set_task_to_serializer_data(serializer, task_id):
setattr(serializer, "_data", data)
def merge_nodes_and_assets(nodes, assets, user):
if not nodes:
return assets
perm_util = UserPermAssetUtil(user=user)
for node_id in nodes:
if node_id == PermNode.FAVORITE_NODE_KEY:
node_assets = perm_util.get_favorite_assets()
elif node_id == PermNode.UNGROUPED_NODE_KEY:
node_assets = perm_util.get_ungroup_assets()
else:
_, node_assets = perm_util.get_node_all_assets(node_id)
assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
return assets
class JobViewSet(OrgBulkModelViewSet):
serializer_class = JobSerializer
filterset_fields = ('name', 'type')
search_fields = ('name', 'comment')
model = Job
_parameters = None
def check_permissions(self, request):
# job: upload_file
@@ -106,10 +91,10 @@ class JobViewSet(OrgBulkModelViewSet):
def perform_create(self, serializer):
run_after_save = serializer.validated_data.pop('run_after_save', False)
node_ids = serializer.validated_data.pop('nodes', [])
assets = serializer.validated_data.get('assets')
assets = merge_nodes_and_assets(node_ids, assets, self.request.user)
serializer.validated_data['assets'] = assets
self._parameters = serializer.validated_data.pop('parameters', None)
nodes = serializer.validated_data.pop('nodes', [])
assets = serializer.validated_data.get('assets', [])
assets = merge_nodes_and_assets(nodes, assets, self.request.user)
if serializer.validated_data.get('type') == Types.upload_file:
account_name = serializer.validated_data.get('runas')
self.check_upload_permission(assets, account_name)
@@ -126,6 +111,8 @@ class JobViewSet(OrgBulkModelViewSet):
def run_job(self, job, serializer):
execution = job.create_execution()
if self._parameters:
execution.parameters = JobExecutionSerializer.validate_parameters(self._parameters)
execution.creator = self.request.user
execution.save()
@@ -300,7 +287,7 @@ class UsernameHintsAPI(APIView):
permission_classes = [IsValidUser]
def post(self, request, **kwargs):
node_ids = request.data.get('nodes', None)
node_ids = request.data.get('nodes', [])
asset_ids = request.data.get('assets', [])
query = request.data.get('query', None)

25
apps/ops/api/variable.py Normal file
View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from rest_framework.decorators import action
from rest_framework.response import Response
from common.api.generic import JMSModelViewSet
from common.const.http import OPTIONS, GET
from common.permissions import IsValidUser
from ..models import Variable
from ..serializers import VariableSerializer, VariableFormDataSerializer
__all__ = [
'VariableViewSet'
]
class VariableViewSet(JMSModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
http_method_names = ['options', 'get']
@action(methods=[GET], detail=False, serializer_class=VariableFormDataSerializer,
permission_classes=[IsValidUser, ], url_path='form_data')
def form_data(self, request, *args, **kwargs):
# 只是为了动态返回serializer fields info
return Response({})

View File

@@ -85,3 +85,8 @@ COMMAND_EXECUTION_DISABLED = _('Command execution disabled')
class Scope(models.TextChoices):
public = 'public', pgettext_lazy("scope", 'Public')
private = 'private', _('Private')
class FieldType(models.TextChoices):
text = 'text', _('Text')
select = 'select', _('Select')

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.1.13 on 2024-10-21 08:02
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0006_database_pg_ssl_mode'),
('ops', '0003_alter_adhoc_unique_together_and_more'),
]
operations = [
migrations.AddField(
model_name='job',
name='nodes',
field=models.ManyToManyField(blank=True, to='assets.node', verbose_name='Node'),
),
migrations.AlterField(
model_name='job',
name='assets',
field=models.ManyToManyField(blank=True, to='assets.asset', verbose_name='Assets'),
),
migrations.AlterUniqueTogether(
name='job',
unique_together={('name', 'org_id', 'creator', 'type')},
),
]

View File

@@ -0,0 +1,53 @@
# Generated by Django 4.1.13 on 2024-10-30 09:38
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),
('ops', '0004_job_nodes_alter_job_assets'),
]
operations = [
migrations.AddField(
model_name='historicaljob',
name='periodic_variable',
field=models.JSONField(default=dict, verbose_name='Periodic variable'),
),
migrations.AddField(
model_name='job',
name='periodic_variable',
field=models.JSONField(default=dict, verbose_name='Periodic variable'),
),
migrations.CreateModel(
name='Variable',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=1024, null=True, verbose_name='Name')),
('var_name', models.CharField(help_text="The variable name used in the script has a fixed prefix 'jms_' followed by the input variable name. For example, if the variable name is 'name,' the final generated environment variable will be 'jms_name'.", max_length=1024, null=True, verbose_name='Variable name')),
('default_value', models.CharField(max_length=2048, null=True, verbose_name='Default Value')),
('type', models.CharField(default='text', max_length=64, verbose_name='Variable type')),
('tips', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Tips')),
('required', models.BooleanField(default=False, verbose_name='Required')),
('extra_args', models.JSONField(default=dict, verbose_name='ExtraVars')),
('adhoc', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.adhoc', verbose_name='Adhoc')),
('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.job', verbose_name='Job')),
('playbook', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.playbook', verbose_name='Playbook')),
],
options={
'verbose_name': 'Variable',
'ordering': ['date_created'],
},
),
]

View File

@@ -5,3 +5,4 @@ from .adhoc import *
from .celery import *
from .playbook import *
from .job import *
from .variable import *

View File

@@ -29,6 +29,7 @@ from ops.ansible.exception import CommandInBlackListException
from ops.mixin import PeriodTaskModelMixin
from ops.variables import *
from ops.const import Types, RunasPolicies, JobStatus, JobModules
from ops.utils import merge_nodes_and_assets
from orgs.mixins.models import JMSOrgBaseModel
from perms.models import AssetPermission
from perms.utils import UserPermAssetUtil
@@ -50,11 +51,13 @@ def get_parent_keys(key, include_self=True):
class JMSPermedInventory(JMSInventory):
def __init__(self,
assets,
nodes,
account_policy='privileged_first',
account_prefer='root,Administrator',
module=None,
host_callback=None,
user=None):
assets = merge_nodes_and_assets(list(nodes), list(assets), user)
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True)
self.user = user
self.module = module
@@ -149,9 +152,11 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL)
type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type"))
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Node"))
use_parameter_define = models.BooleanField(default=False, verbose_name=(_('Use Parameter Define')))
parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define'))
periodic_variable = models.JSONField(default=dict, verbose_name=_('Periodic variable'))
runas = models.CharField(max_length=128, default='root', verbose_name=_('Run as'))
runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip,
verbose_name=_('Run as policy'))
@@ -203,7 +208,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
@property
def inventory(self):
return JMSPermedInventory(self.assets.all(),
return JMSPermedInventory(self.assets.all(), self.nodes.all(),
self.runas_policy, self.runas,
user=self.creator, module=self.module)
@@ -220,7 +225,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
class Meta:
verbose_name = _("Job")
unique_together = [('name', 'org_id', 'creator')]
unique_together = [('name', 'org_id', 'creator', 'type')]
ordering = ['date_created']
@@ -328,7 +333,7 @@ class JobExecution(JMSOrgBaseModel):
if isinstance(self.parameters, str):
extra_vars = json.loads(self.parameters)
else:
extra_vars = {}
extra_vars = self.parameters if self.parameters else {}
static_variables = self.gather_static_variables()
extra_vars.update(static_variables)
@@ -349,7 +354,8 @@ class JobExecution(JMSOrgBaseModel):
runner = PlaybookRunner(
self.inventory_path,
self.current_job.playbook.entry,
self.private_dir
self.private_dir,
extra_vars=extra_vars,
)
elif self.current_job.type == Types.upload_file:
job_id = self.current_job.id

View File

@@ -23,8 +23,6 @@ dangerous_keywords = (
)
class Playbook(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'), null=True)

View File

@@ -0,0 +1,50 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
from ops.const import FieldType
class Variable(JMSBaseModel):
name = models.CharField(max_length=1024, verbose_name=_('Name'), null=True)
var_name = models.CharField(
max_length=1024, null=True, verbose_name=_('Variable name'),
help_text=_("The variable name used in the script has a fixed prefix 'jms_' followed by the input variable "
"name. For example, if the variable name is 'name,' the final generated environment variable will "
"be 'jms_name'.")
)
default_value = models.CharField(max_length=2048, verbose_name=_('Default Value'), null=True)
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
type = models.CharField(max_length=64, default=FieldType.text, verbose_name=_('Variable type'))
tips = models.CharField(max_length=1024, default='', verbose_name=_('Tips'), null=True, blank=True)
required = models.BooleanField(default=False, verbose_name=_('Required'))
extra_args = models.JSONField(default=dict, verbose_name=_('ExtraVars'))
playbook = models.ForeignKey(
'ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.CASCADE, related_name='variable'
)
adhoc = models.ForeignKey(
'ops.AdHoc', verbose_name=_("Adhoc"), null=True, on_delete=models.CASCADE, related_name='variable'
)
job = models.ForeignKey('ops.Job', verbose_name=_("Job"), null=True, on_delete=models.CASCADE,
related_name='variable')
def __str__(self):
return self.name
@property
def form_data(self):
return {
'var_name': self.var_name,
'label': self.name,
'help_text': self.tips,
'read_only': False,
'required': self.required,
'type': self.type,
'write_only': False,
'default': self.default_value,
'extra_args': self.extra_args,
}
class Meta:
verbose_name = _("Variable")
ordering = ['date_created']

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
#
from .celery import *
from .variable import *
from .adhoc import *

View File

@@ -3,17 +3,20 @@ from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField, LabeledChoiceField
from common.serializers import WritableNestedModelSerializer
from common.serializers.fields import ReadableHiddenField
from common.serializers.mixin import CommonBulkModelSerializer
from .mixin import ScopeSerializerMixin
from ..const import Scope
from ..models import AdHoc
from ops.serializers import AdhocVariableSerializer
class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer, WritableNestedModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
variable = AdhocVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
class Meta:
model = AdHoc
read_only_field = ["id", "creator", "date_created", "date_updated", "created_by"]
fields = read_only_field + ["id", "name", "scope", "module", "args", "comment"]
fields_m2m = ['variable']
fields = read_only_field + fields_m2m + ["id", "name", "scope", "module", "args", "comment"]

View File

@@ -3,20 +3,25 @@ import uuid
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.models import Asset
from common.serializers.fields import ReadableHiddenField
from assets.models import Asset, Node
from common.serializers import WritableNestedModelSerializer
from common.serializers.fields import ReadableHiddenField, ObjectRelatedField
from ops.mixin import PeriodTaskSerializerMixin
from ops.models import Job, JobExecution
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ops.serializers import JobVariableSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin, WritableNestedModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Execute after saving"), default=False, required=False)
nodes = serializers.ListField(required=False, child=serializers.CharField())
date_last_run = serializers.DateTimeField(label=_('Date last run'), read_only=True)
name = serializers.CharField(label=_('Name'), max_length=128, allow_blank=True, required=False)
assets = serializers.PrimaryKeyRelatedField(label=_('Assets'), queryset=Asset.objects, many=True, required=False)
nodes = ObjectRelatedField(label=_('Nodes'), queryset=Node.objects, many=True, required=False)
variable = JobVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
parameters = serializers.JSONField(label=_('Parameters'), default={}, write_only=True, required=False,
allow_null=True)
def to_internal_value(self, data):
instant = data.get('instant', False)
@@ -39,6 +44,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
"id", "date_last_run", "date_created",
"date_updated", "average_time_cost"
]
fields_m2m = ['variable']
fields = read_only_fields + [
"name", "instant", "type", "module",
"args", "playbook", "assets",
@@ -46,8 +52,8 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
"use_parameter_define", "parameters_define",
"timeout", "chdir", "comment", "summary",
"is_periodic", "interval", "crontab", "nodes",
"run_after_save"
]
"run_after_save", "parameters", "periodic_variable"
] + fields_m2m
extra_kwargs = {
'average_time_cost': {'label': _('Duration')},
}
@@ -97,3 +103,13 @@ class JobExecutionSerializer(BulkOrgResourceModelSerializer):
if job_obj.creator != self.context['request'].user:
raise serializers.ValidationError(_("You do not have permission for the current job."))
return job_obj
@staticmethod
def validate_parameters(parameters):
prefix = "jms_"
new_parameters = {}
for key, value in parameters.items():
if not key.startswith("jms_"):
key = prefix + key
new_parameters[key] = value
return new_parameters

View File

@@ -1,11 +1,13 @@
import os
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers import WritableNestedModelSerializer
from common.serializers.fields import ReadableHiddenField
from common.serializers.mixin import CommonBulkModelSerializer
from ops.models import Playbook
from .mixin import ScopeSerializerMixin
from ops.serializers.variable import PlaybookVariableSerializer
def parse_playbook_name(path):
@@ -13,10 +15,11 @@ def parse_playbook_name(path):
return file_name.split(".")[-2]
class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer, WritableNestedModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
path = serializers.FileField(required=False)
variable = PlaybookVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
def to_internal_value(self, data):
name = data.get('name', False)
if not name and data.get('path'):
@@ -26,7 +29,8 @@ class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
class Meta:
model = Playbook
read_only_fields = ["id", "date_created", "date_updated", "created_by"]
fields = read_only_fields + [
fields_m2m = ['variable']
fields = read_only_fields + fields_m2m + [
"id", 'path', 'scope', "name", "comment", "creator",
'create_method', 'vcs_url',
]

View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField, LabeledChoiceField, EncryptedField
from common.serializers.mixin import CommonBulkModelSerializer
from ops.const import FieldType
from ops.models import Variable, AdHoc, Job, Playbook
__all__ = [
'VariableSerializer', 'AdhocVariableSerializer', 'JobVariableSerializer', 'PlaybookVariableSerializer',
'VariableFormDataSerializer'
]
class VariableSerializer(CommonBulkModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
type = LabeledChoiceField(
choices=FieldType.choices, default=FieldType.text, label=_("Variable Type")
)
extra_args = serializers.CharField(
max_length=1024, label=_("ExtraVars"), required=False, allow_blank=True,
help_text=_(
"Each item is on a separate line, with each line separated by a colon. The part before the colon is the "
"display content, and the part after the colon is the value.")
)
class Meta:
model = Variable
read_only_fields = ["id", "date_created", "date_updated", "created_by", "creator"]
fields = read_only_fields + [
"name", "var_name", "type", 'required', 'default_value', 'tips', 'adhoc', 'playbook', 'job', 'form_data',
'extra_args'
]
def validate(self, attrs):
attrs = super().validate(attrs)
type = attrs.get('type')
attrs['extra_args'] = {}
if type == FieldType.text:
attrs['default_value'] = self.initial_data.get('text_default_value')
elif type == FieldType.select:
attrs['default_value'] = self.initial_data.get('select_default_value')
options = self.initial_data.get('extra_args', '')
attrs['extra_args'] = {"options": options}
return attrs
def to_representation(self, instance):
data = super().to_representation(instance)
if instance.type == FieldType.select:
data['extra_args'] = instance.extra_args.get('options', '')
data['select_default_value'] = instance.default_value
if instance.type == FieldType.text:
data['text_default_value'] = instance.default_value
return data
@classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset.prefetch_related('adhoc', 'job', 'playbook')
return queryset
class AdhocVariableSerializer(VariableSerializer):
adhoc = serializers.PrimaryKeyRelatedField(queryset=AdHoc.objects, required=False)
class Meta(VariableSerializer.Meta):
fields = VariableSerializer.Meta.fields
class JobVariableSerializer(VariableSerializer):
job = serializers.PrimaryKeyRelatedField(queryset=Job.objects, required=False)
class Meta(VariableSerializer.Meta):
fields = VariableSerializer.Meta.fields
class PlaybookVariableSerializer(VariableSerializer):
playbook = serializers.PrimaryKeyRelatedField(queryset=Playbook.objects, required=False)
class Meta(VariableSerializer.Meta):
fields = VariableSerializer.Meta.fields
def create_dynamic_text_choices(options):
"""
动态创建一个 TextChoices 子类。`options` 应该是一个列表,
格式为 [(value1, display1), (value2, display2), ...]
"""
attrs = {
key.upper(): value for value, key in options
}
attrs['choices'] = options
return type('DynamicTextChoices', (models.TextChoices,), attrs)
class VariableFormDataSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
request = self.context.get('request')
if not request:
return
params = request.query_params
job = params.get('job')
adhoc = params.get('adhoc')
playbook = params.get('playbook')
if job:
variables = Variable.objects.filter(job=job).all()
elif adhoc:
variables = Variable.objects.filter(adhoc=adhoc).all()
else:
variables = Variable.objects.filter(playbook=playbook).all()
dynamic_fields = [var.form_data for var in variables]
if dynamic_fields:
for field in dynamic_fields:
field_type = field['type']
required = field['required']
var_name = field["var_name"]
label = field["label"]
help_text = field['help_text']
default = field['default']
if field_type == FieldType.text:
self.fields[var_name] = serializers.CharField(
max_length=1024, label=label, help_text=help_text, required=required
)
elif field_type == FieldType.select:
extra_args = field.get('extra_args', {})
options = extra_args.get('options', '').splitlines()
DynamicFieldType = models.TextChoices(
'DynamicFieldType',
{
option.split(':')[0]: option.split(':')[1] for option in
options
}
)
self.fields[var_name] = LabeledChoiceField(
choices=DynamicFieldType.choices, required=required, label=label,
help_text=help_text
)
if required and default is not None:
self.fields[var_name].default = default

View File

@@ -10,6 +10,7 @@ from django_celery_beat.models import PeriodicTask
from common.const.crontab import CRONTAB_AT_AM_TWO
from common.utils import get_logger, get_object_or_none, get_log_keep_day
from ops.celery import app
from ops.serializers.job import JobExecutionSerializer
from orgs.utils import tmp_to_org, tmp_to_root_org
from .celery.decorator import (
register_as_period_task, after_app_ready_start
@@ -64,6 +65,8 @@ def run_ops_job(job_id):
with tmp_to_org(job.org):
execution = job.create_execution()
execution.creator = job.creator
if job.periodic_variable:
execution.parameters = JobExecutionSerializer.validate_parameters(job.periodic_variable)
_run_ops_job_execution(execution)

View File

@@ -15,8 +15,8 @@ bulk_router = BulkRouter()
bulk_router.register(r'adhocs', api.AdHocViewSet, 'adhoc')
bulk_router.register(r'playbooks', api.PlaybookViewSet, 'playbook')
bulk_router.register(r'jobs', api.JobViewSet, 'job')
bulk_router.register(r'variable', api.VariableViewSet, 'variable')
bulk_router.register(r'job-executions', api.JobExecutionViewSet, 'job-execution')
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
router.register(r'tasks', api.CeleryTaskViewSet, 'task')

View File

@@ -5,6 +5,9 @@ from django.conf import settings
from common.utils import get_logger, make_dirs
from jumpserver.const import PROJECT_DIR
from perms.models import PermNode
from perms.utils import UserPermAssetUtil
from assets.models import Asset, Node
logger = get_logger(__file__)
@@ -29,3 +32,19 @@ def get_ansible_log_verbosity(verbosity=0):
return 1
return verbosity
def merge_nodes_and_assets(nodes, assets, user):
if not nodes:
return assets
perm_util = UserPermAssetUtil(user=user)
for node_id in nodes:
if isinstance(node_id, Node):
node_id = node_id.id
if node_id == PermNode.FAVORITE_NODE_KEY:
node_assets = perm_util.get_favorite_assets()
elif node_id == PermNode.UNGROUPED_NODE_KEY:
node_assets = perm_util.get_ungroup_assets()
else:
_, node_assets = perm_util.get_node_all_assets(node_id)
assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
return assets