perf: change secret automation api (#9028)

Co-authored-by: feng <1304903146@qq.com>
pull/9029/head
fit2bot 2022-11-08 17:54:51 +08:00 committed by GitHub
parent e69bb9f83e
commit ce9ebd94ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 488 additions and 116 deletions

View File

@ -6,5 +6,6 @@ from .label import *
from .account import *
from .node import *
from .domain import *
from .automations import *
from .gathered_user import *
from .favorite_asset import *

View File

@ -0,0 +1,3 @@
from .base import *
from .change_secret import *
from .gather_accounts import *

View File

@ -0,0 +1,118 @@
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from rest_framework.response import Response
from rest_framework import status, mixins, viewsets
from orgs.mixins import generics
from assets import serializers
from assets.const import AutomationTypes
from assets.tasks import execute_automation
from assets.models import BaseAutomation, AutomationExecution
from common.const.choices import Trigger
__all__ = [
'AutomationAssetsListApi', 'AutomationRemoveAssetApi',
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet'
]
class AutomationAssetsListApi(generics.ListAPIView):
serializer_class = serializers.AutomationAssetsSerializer
filter_fields = ("name", "address")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(BaseAutomation, pk=pk)
def get_queryset(self):
instance = self.get_object()
assets = instance.get_all_assets().only(
*self.serializer_class.Meta.only_fields
)
return assets
class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateAssetSerializer
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response({'error': serializer.errors})
assets = serializer.validated_data.get('assets')
if assets:
instance.assets.remove(*tuple(assets))
return Response({'msg': 'ok'})
class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateAssetSerializer
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
assets = serializer.validated_data.get('assets')
if assets:
instance.assets.add(*tuple(assets))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateAssetSerializer
def update(self, request, *args, **kwargs):
action_params = ['add', 'remove']
action = request.query_params.get('action')
if action not in action_params:
err_info = _("The parameter 'action' must be [{}]".format(','.join(action_params)))
return Response({"error": err_info})
instance = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
nodes = serializer.validated_data.get('nodes')
if nodes:
# eg: plan.nodes.add(*tuple(assets))
getattr(instance.nodes, action)(*tuple(nodes))
return Response({"msg": "ok"})
else:
return Response({"error": serializer.errors})
class AutomationExecutionViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
search_fields = ('trigger',)
filterset_fields = ('trigger', 'automation_id')
serializer_class = serializers.AutomationExecutionSerializer
def get_queryset(self):
queryset = AutomationExecution.objects.all()
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = queryset.order_by('-date_start')
return queryset
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
automation = serializer.validated_data.get('automation')
tp = serializer.validated_data.get('type')
model = AutomationTypes.get_model(tp)
task = execute_automation.delay(
pid=automation.ok, trigger=Trigger.manual, model=model
)
return Response({'task': task.id}, status=status.HTTP_201_CREATED)

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
from rest_framework import mixins
from common.utils import get_object_or_none
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from assets import serializers
__all__ = [
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet'
]
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
model = ChangeSecretAutomation
filter_fields = ('name', 'secret_type', 'secret_strategy')
search_fields = filter_fields
ordering_fields = ('name',)
serializer_class = serializers.ChangeSecretAutomationSerializer
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
filter_fields = ['username', 'asset', 'reason', 'execution']
search_fields = ['username', 'reason', 'asset__hostname']
def get_queryset(self):
return ChangeSecretRecord.objects.all()
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
eid = self.request.GET.get('execution_id')
execution = get_object_or_none(AutomationExecution, pk=eid)
if execution:
queryset = queryset.filter(execution=execution)
queryset = queryset.order_by('is_success', '-date_start')
return queryset

View File

@ -47,7 +47,7 @@ class PushOrVerifyHostCallbackMixin:
secret = account.secret
private_key_path = None
if account.secret_type == SecretType.ssh_key:
if account.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(secret, path_dir)
secret = self.generate_public_key(secret)

View File

@ -89,9 +89,9 @@ class ChangeSecretManager(BasePlaybookManager):
return self.generate_password()
def get_secret(self):
if self.secret_type == SecretType.ssh_key:
if self.secret_type == SecretType.SSH_KEY:
secret = self.get_ssh_key()
elif self.secret_type == SecretType.password:
elif self.secret_type == SecretType.PASSWORD:
secret = self.get_password()
else:
raise ValueError("Secret must be set")
@ -99,7 +99,7 @@ class ChangeSecretManager(BasePlaybookManager):
def get_kwargs(self, account, secret):
kwargs = {}
if self.secret_type != SecretType.ssh_key:
if self.secret_type != SecretType.SSH_KEY:
return kwargs
kwargs['strategy'] = self.execution.snapshot['ssh_key_change_strategy']
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
@ -143,7 +143,7 @@ class ChangeSecretManager(BasePlaybookManager):
self.name_recorder_mapper[h['name']] = recorder
private_key_path = None
if self.secret_type == SecretType.ssh_key:
if self.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)

View File

@ -21,14 +21,14 @@ class PingManager(BasePlaybookManager):
def on_host_success(self, host, result):
asset, account = self.host_asset_and_account_mapper.get(host)
asset.set_connectivity(Connectivity.ok)
asset.set_connectivity(Connectivity.OK)
if not account:
return
account.set_connectivity(Connectivity.ok)
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
asset, account = self.host_asset_and_account_mapper.get(host)
asset.set_connectivity(Connectivity.failed)
asset.set_connectivity(Connectivity.FAILED)
if not account:
return
account.set_connectivity(Connectivity.failed)
account.set_connectivity(Connectivity.FAILED)

View File

@ -18,8 +18,8 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
def on_host_success(self, host, result):
account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.ok)
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.failed)
account.set_connectivity(Connectivity.FAILED)

View File

@ -3,13 +3,13 @@ from django.utils.translation import ugettext_lazy as _
class Connectivity(TextChoices):
unknown = 'unknown', _('Unknown')
ok = 'ok', _('Ok')
failed = 'failed', _('Failed')
UNKNOWN = 'unknown', _('Unknown')
OK = 'ok', _('Ok')
FAILED = 'failed', _('Failed')
class SecretType(TextChoices):
password = 'password', _('Password')
ssh_key = 'ssh_key', _('SSH key')
access_key = 'access_key', _('Access key')
token = 'token', _('Token')
PASSWORD = 'password', _('Password')
SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token')

View File

@ -17,6 +17,22 @@ class AutomationTypes(TextChoices):
verify_account = 'verify_account', _('Verify account')
gather_accounts = 'gather_accounts', _('Gather accounts')
@classmethod
def get_type_model(cls, tp):
from assets.models import (
PingAutomation, GatherFactsAutomation, PushAccountAutomation,
ChangeSecretAutomation, VerifyAccountAutomation, GatherAccountsAutomation,
)
type_model_dict = {
cls.ping: PingAutomation,
cls.gather_facts: GatherFactsAutomation,
cls.push_account: PushAccountAutomation,
cls.change_secret: ChangeSecretAutomation,
cls.verify_account: VerifyAccountAutomation,
cls.gather_accounts: GatherAccountsAutomation,
}
return type_model_dict.get(tp)
class SecretStrategy(TextChoices):
custom = 'specific', _('Specific')

View File

@ -1,7 +1,8 @@
from .change_secret import *
from .discovery_account import *
from .ping import *
from .base import *
from .push_account import *
from .gather_facts import *
from .gather_accounts import *
from .change_secret import *
from .verify_account import *
from .ping import *
from .gather_accounts import *
from .discovery_account import *

View File

@ -3,7 +3,7 @@ from celery import current_task
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.const.choices import Trigger, Status
from common.const.choices import Trigger
from common.mixins.models import CommonModelMixin
from common.db.fields import EncryptJsonDictTextField
from orgs.mixins.models import OrgModelMixin

View File

@ -12,7 +12,7 @@ __all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord']
class ChangeSecretAutomation(BaseAutomation):
secret_type = models.CharField(
choices=SecretType.choices, max_length=16,
default=SecretType.password, verbose_name=_('Secret type')
default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret_strategy = models.CharField(
choices=SecretStrategy.choices, max_length=16,
@ -24,7 +24,7 @@ class ChangeSecretAutomation(BaseAutomation):
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
recipients = models.ManyToManyField('users.User', blank=True, verbose_name=_("Recipient"))
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
def save(self, *args, **kwargs):
self.type = AutomationTypes.change_secret

View File

@ -24,7 +24,7 @@ logger = get_logger(__file__)
class AbsConnectivity(models.Model):
connectivity = models.CharField(
choices=Connectivity.choices, default=Connectivity.unknown,
choices=Connectivity.choices, default=Connectivity.UNKNOWN,
max_length=16, verbose_name=_('Connectivity')
)
date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified"))
@ -50,7 +50,7 @@ class BaseAccount(JMSOrgBaseModel):
name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField(
max_length=16, choices=SecretType.choices, default=SecretType.password, verbose_name=_('Secret type')
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
@ -65,25 +65,25 @@ class BaseAccount(JMSOrgBaseModel):
@property
def specific(self):
data = {}
if self.secret_type != SecretType.ssh_key:
if self.secret_type != SecretType.SSH_KEY:
return data
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
return data
@property
def private_key(self):
if self.secret_type == SecretType.ssh_key:
if self.secret_type == SecretType.SSH_KEY:
return self.secret
return None
@private_key.setter
def private_key(self, value):
self.secret = value
self.secret_type = SecretType.ssh_key
self.secret_type = SecretType.SSH_KEY
@lazyproperty
def public_key(self):
if self.secret_type == SecretType.ssh_key:
if self.secret_type == SecretType.SSH_KEY:
return ssh_pubkey_gen(private_key=self.private_key)
return None
@ -113,7 +113,7 @@ class BaseAccount(JMSOrgBaseModel):
@property
def private_key_path(self):
if not self.secret_type != SecretType.ssh_key or not self.secret:
if not self.secret_type != SecretType.SSH_KEY or not self.secret:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')

View File

@ -11,4 +11,4 @@ from .account import *
from assets.serializers.account.backup import *
from .platform import *
from .cagegory import *
from .automation import *
from .automations import *

View File

@ -1,35 +0,0 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.utils import get_logger
from assets.models import ChangeSecretRecord
logger = get_logger(__file__)
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
asset = serializers.SerializerMethodField(label=_('Asset'))
account = serializers.SerializerMethodField(label=_('Account'))
is_success = serializers.SerializerMethodField(label=_('Is success'))
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'asset', 'account', 'old_secret', 'new_secret',
'status', 'error', 'is_success'
]
@staticmethod
def get_asset(instance):
return str(instance.asset)
@staticmethod
def get_account(instance):
return str(instance.account)
@staticmethod
def get_is_success(obj):
if obj.status == 'success':
return _("Success")
return _("Failed")

View File

@ -0,0 +1,3 @@
from .base import *
from .change_secret import *
from .gather_accounts import *

View File

@ -0,0 +1,76 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
from ops.mixin import PeriodTaskSerializerMixin
from assets.const import AutomationTypes
from assets.models import Asset, BaseAutomation, AutomationExecution
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.utils import get_logger
logger = get_logger(__file__)
__all__ = [
'BaseAutomationSerializer', 'AutomationExecutionSerializer',
'UpdateAssetSerializer', 'UpdateNodeSerializer', 'AutomationAssetsSerializer',
]
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
class Meta:
read_only_fields = [
'date_created', 'date_updated', 'created_by', 'periodic_display'
]
fields = read_only_fields + [
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
'type', 'accounts', 'nodes', 'assets', 'is_active'
]
extra_kwargs = {
'name': {'required': True},
'periodic_display': {'label': _('Periodic perform')},
}
class AutomationExecutionSerializer(serializers.ModelSerializer):
snapshot = serializers.SerializerMethodField(label=_('Automation snapshot'))
type = serializers.ChoiceField(choices=AutomationTypes.choices, write_only=True, label=_('Type'))
trigger_display = serializers.ReadOnlyField(source='get_trigger_display', label=_('Trigger mode'))
class Meta:
model = AutomationExecution
fields = [
'id', 'automation', 'trigger', 'trigger_display',
'date_start', 'date_finished', 'snapshot', 'type'
]
@staticmethod
def get_snapshot(obj):
tp = obj.snapshot['type']
snapshot = {
'type': tp,
'name': obj.snapshot['name'],
'comment': obj.snapshot['comment'],
'accounts': obj.snapshot['accounts'],
'node_amount': len(obj.snapshot['nodes']),
'asset_amount': len(obj.snapshot['assets']),
'type_display': getattr(AutomationTypes, tp).label,
}
return snapshot
class UpdateAssetSerializer(serializers.ModelSerializer):
class Meta:
model = BaseAutomation
fields = ['id', 'assets']
class UpdateNodeSerializer(serializers.ModelSerializer):
class Meta:
model = BaseAutomation
fields = ['id', 'nodes']
class AutomationAssetsSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = ['id', 'name', 'address']
fields = tuple(only_fields)

View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from rest_framework import serializers
from assets.serializers.base import AuthValidateMixin
from assets.models import ChangeSecretAutomation, ChangeSecretRecord
from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy
from common.utils import get_logger
from .base import BaseAutomationSerializer
logger = get_logger(__file__)
__all__ = [
'ChangeSecretAutomationSerializer',
'ChangeSecretRecordSerializer',
'ChangeSecretRecordBackUpSerializer'
]
class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializer):
password_rules = serializers.DictField(default=DEFAULT_PASSWORD_RULES)
secret_strategy_display = serializers.ReadOnlyField(
source='get_secret_strategy_display', label=_('Secret strategy')
)
ssh_key_change_strategy_display = serializers.ReadOnlyField(
source='get_ssh_key_strategy_display', label=_('SSH Key strategy')
)
class Meta:
model = ChangeSecretAutomation
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + [
'secret_strategy_display', 'ssh_key_change_strategy_display'
]
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
'secret_type', 'secret_strategy', 'secret', 'password_rules',
'ssh_key_change_strategy', 'passphrase', 'recipients',
]
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
'recipients': {'label': _('Recipient'), 'help_text': _(
"Currently only mail sending is supported"
)},
}}
def validate_password_rules(self, password_rules):
secret_type = self.initial_secret_type
if secret_type != SecretType.PASSWORD:
return password_rules
length = password_rules.get('length')
symbol_set = password_rules.get('symbol_set', '')
try:
length = int(length)
except Exception as e:
logger.error(e)
msg = _("* Please enter the correct password length")
raise serializers.ValidationError(msg)
if length < 6 or length > 30:
msg = _('* Password length range 6-30 bits')
raise serializers.ValidationError(msg)
if not isinstance(symbol_set, str):
symbol_set = str(symbol_set)
password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)}
return password_rules
def validate(self, attrs):
secret_type = attrs.get('secret_type')
secret_strategy = attrs.get('secret_strategy')
if secret_type == SecretType.PASSWORD:
attrs.pop('ssh_key_change_strategy', None)
if secret_strategy == SecretStrategy.custom:
attrs.pop('password_rules', None)
else:
attrs.pop('secret', None)
elif secret_type == SecretType.SSH_KEY:
attrs.pop('password_rules', None)
if secret_strategy != SecretStrategy.custom:
attrs.pop('secret', None)
return attrs
class ChangeSecretRecordSerializer(serializers.ModelSerializer):
asset_display = serializers.SerializerMethodField(label=_('Asset display'))
account_display = serializers.SerializerMethodField(label=_('Account display'))
is_success = serializers.SerializerMethodField(label=_('Is success'))
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'asset', 'account', 'date_started', 'date_finished',
'is_success', 'error', 'execution', 'asset_display', 'account_display'
]
read_only_fields = fields
@staticmethod
def get_asset_display(instance):
return str(instance.asset)
@staticmethod
def get_account_display(instance):
return str(instance.account)
@staticmethod
def get_is_success(obj):
if obj.status == 'success':
return _("Success")
return _("Failed")
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
asset = serializers.SerializerMethodField(label=_('Asset'))
account = serializers.SerializerMethodField(label=_('Account'))
is_success = serializers.SerializerMethodField(label=_('Is success'))
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'asset', 'account', 'old_secret', 'new_secret',
'status', 'error', 'is_success'
]
read_only_fields = fields
@staticmethod
def get_asset(instance):
return str(instance.asset)
@staticmethod
def get_account(instance):
return str(instance.account)
@staticmethod
def get_is_success(obj):
if obj.status == 'success':
return _("Success")
return _("Failed")

View File

@ -1,68 +1,51 @@
# -*- coding: utf-8 -*-
#
from io import StringIO
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
from common.drf.fields import EncryptedField
from .utils import validate_password_for_ansible
from assets.const import SecretType
from .utils import validate_password_for_ansible, validate_ssh_key
class AuthValidateMixin(serializers.Serializer):
password = EncryptedField(
label=_('Password'), required=False, allow_blank=True, allow_null=True,
max_length=1024, validators=[validate_password_for_ansible]
)
private_key = EncryptedField(
label=_('SSH private key'), required=False, allow_blank=True,
allow_null=True, max_length=16384
secret_type = serializers.CharField(label=_('Secret type'), max_length=16, required=True)
secret = EncryptedField(
label=_('Secret'), required=False, max_length=16384, allow_blank=True,
allow_null=True, write_only=True,
)
passphrase = serializers.CharField(
allow_blank=True, allow_null=True, required=False, max_length=512,
write_only=True, label=_('Key password')
)
def validate_private_key(self, private_key):
if not private_key:
return
passphrase = self.initial_data.get('passphrase')
passphrase = passphrase if passphrase else None
valid = validate_ssh_private_key(private_key, password=passphrase)
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
@property
def initial_secret_type(self):
secret_type = self.initial_data.get('secret_type')
return secret_type
private_key = ssh_private_key_gen(private_key, password=passphrase)
string_io = StringIO()
private_key.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key
def validate_secret(self, secret):
if not secret:
return
secret_type = self.initial_secret_type
if secret_type == SecretType.PASSWORD:
validate_password_for_ansible(secret)
return secret
elif secret_type == SecretType.SSH_KEY:
passphrase = self.initial_data.get('passphrase')
passphrase = passphrase if passphrase else None
return validate_ssh_key(secret, passphrase)
else:
return secret
@staticmethod
def clean_auth_fields(validated_data):
for field in ('password', 'private_key', 'public_key'):
for field in ('secret',):
value = validated_data.get(field)
if not value:
validated_data.pop(field, None)
validated_data.pop('passphrase', None)
@staticmethod
def _validate_gen_key(attrs):
private_key = attrs.get('private_key')
if not private_key:
return attrs
password = attrs.get('passphrase')
username = attrs.get('username')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
attrs['public_key'] = public_key
return attrs
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
return super().validate(attrs)
def create(self, validated_data):
self.clean_auth_fields(validated_data)
return super().create(validated_data)

View File

@ -1,6 +1,10 @@
from io import StringIO
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.utils import ssh_private_key_gen, validate_ssh_private_key
def validate_password_for_ansible(password):
""" 校验 Ansible 不支持的特殊字符 """
@ -15,3 +19,14 @@ def validate_password_for_ansible(password):
if '"' in password:
raise serializers.ValidationError(_('Password can not contains `"` '))
def validate_ssh_key(ssh_key, passphrase=None):
valid = validate_ssh_private_key(ssh_key, password=passphrase)
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
ssh_key = ssh_private_key_gen(ssh_key, password=passphrase)
string_io = StringIO()
ssh_key.write_private_key(string_io)
ssh_key = string_io.getvalue()
return ssh_key

View File

@ -7,9 +7,9 @@ logger = get_logger(__file__)
@shared_task(queue='ansible')
def execute_automation(pid, trigger, mode):
def execute_automation(pid, trigger, model):
with tmp_to_root_org():
instance = get_object_or_none(mode, pk=pid)
instance = get_object_or_none(model, pk=pid)
if not instance:
logger.error("No automation task found: {}".format(pid))
return

View File

@ -27,17 +27,25 @@ router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automations')
router.register(r'automation-executions', api.AutomationExecutionViewSet, 'automation-execution')
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-records')
urlpatterns = [
# path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'),
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'),
path('assets/<uuid:pk>/perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'),
path('assets/<uuid:pk>/perm-users/<uuid:perm_user_id>/permissions/', api.AssetPermUserPermissionsListApi.as_view(), name='asset-perm-user-permission-list'),
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'),
path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
path('assets/<uuid:pk>/perm-users/<uuid:perm_user_id>/permissions/', api.AssetPermUserPermissionsListApi.as_view(),
name='asset-perm-user-permission-list'),
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(),
name='asset-perm-user-group-list'),
path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/',
api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(), name='account-secret-history'),
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(),
name='account-secret-history'),
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
@ -52,7 +60,11 @@ urlpatterns = [
path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'),
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
path('automation/<uuid:pk>/asset/remove/', api.AutomationRemoveAssetApi.as_view(), name='automation-remove-asset'),
path('automation/<uuid:pk>/asset/add/', api.AutomationAddAssetApi.as_view(), name='automation-add-asset'),
path('automation/<uuid:pk>/nodes/', api.AutomationNodeAddRemoveApi.as_view(), name='automation-add-or-remove-node'),
path('automation/<uuid:pk>/assets/', api.AutomationAssetsListApi.as_view(), name='automation-assets'),
]
urlpatterns += router.urls