perf: Push account recorder

pull/14842/head
feng 2025-01-23 17:19:06 +08:00 committed by feng626
parent a121bf41fe
commit 3fca8eaead
7 changed files with 154 additions and 72 deletions

View File

@ -187,7 +187,7 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
@property
def latest_change_secret_record(self) -> ChangeSecretRecord:
return self.account.change_secret_records.filter(
return self.account.changesecretrecords.filter(
status=ChangeSecretRecordStatusChoice.pending
).order_by('-date_created').first()

View File

@ -1,13 +1,15 @@
from copy import deepcopy
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.automations.methods import platform_automation_methods
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType, ChangeSecretRecordStatusChoice
from accounts.models import BaseAccountQuerySet
from assets.automations.base.manager import BasePlaybookManager
from assets.const import HostTypes
from common.db.utils import safe_db_connection
from common.utils import get_logger
logger = get_logger(__name__)
@ -32,6 +34,8 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
'ssh_key_change_strategy', SSHKeyStrategy.set_jms
)
self.account_ids = self.execution.snapshot['accounts']
self.record_map = self.execution.snapshot.get('record_map', {}) # 这个是某个失败的记录重试
self.name_recorder_mapper = {} # 做个映射,方便后面处理
def gen_account_inventory(self, account, asset, h, path_dir):
raise NotImplementedError
@ -119,3 +123,51 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.date_finished = timezone.now()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = getattr(recorder, 'new_secret', account.secret)
account.date_updated = timezone.now()
with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'date_updated'])
self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append(
{
"asset": str(account.asset),
"username": account.username,
}
)
super().on_host_success(host, result)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
}
)
super().on_host_error(host, error, result)

View File

@ -2,7 +2,6 @@ import os
import time
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
@ -12,7 +11,6 @@ from accounts.const import (
from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from common.db.utils import safe_db_connection
from common.decorators import bulk_create_decorator
from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file
@ -26,11 +24,6 @@ logger = get_logger(__name__)
class ChangeSecretManager(BaseChangeSecretPushManager):
ansible_account_prefer = ''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.record_map = self.execution.snapshot.get('record_map', {}) # 这个是某个失败的记录重试
self.name_recorder_mapper = {} # 做个映射,方便后面处理
@classmethod
def method_type(cls):
return AutomationTypes.change_secret
@ -74,54 +67,6 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
)
return recorder
def on_host_success(self, host, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.date_finished = timezone.now()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = recorder.new_secret
account.date_updated = timezone.now()
with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'date_updated'])
self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append(
{
"asset": str(account.asset),
"username": account.username,
}
)
super().on_host_success(host, result)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
}
)
super().on_host_success(host, result)
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
and not self.execution.snapshot['secret']:

View File

@ -1,9 +1,11 @@
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from common.decorators import bulk_create_decorator
from common.utils import get_logger
from common.utils.timezone import local_now_filename
from ..base.manager import BaseChangeSecretPushManager
from ...models import PushSecretRecord
logger = get_logger(__name__)
@ -17,12 +19,33 @@ class PushAccountManager(BaseChangeSecretPushManager):
return account.secret
def gen_account_inventory(self, account, asset, h, path_dir):
self.get_or_create_record(asset, account, h['name'])
secret = self.get_secret(account)
secret_type = account.secret_type
new_secret, private_key_path = self.handle_ssh_secret(secret_type, secret, path_dir)
h = self.gen_inventory(h, account, new_secret, private_key_path, asset)
return h
def get_or_create_record(self, asset, account, name):
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id in self.record_map:
record_id = self.record_map[asset_account_id]
recorder = PushSecretRecord.objects.filter(id=record_id).first()
else:
recorder = self.create_record(asset, account)
self.name_recorder_mapper[name] = recorder
return recorder
@bulk_create_decorator(PushSecretRecord)
def create_record(self, asset, account):
recorder = PushSecretRecord(
asset=asset, account=account, execution=self.execution,
comment=f'{account.username}@{asset.address}'
)
return recorder
def print_summary(self):
print('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end')

View File

@ -0,0 +1,51 @@
# Generated by Django 4.1.13 on 2025-01-23 07:22
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0011_auto_20241204_1516'),
('accounts', '0028_remove_checkaccountengine_is_active_and_more'),
]
operations = [
migrations.AlterField(
model_name='changesecretrecord',
name='account',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='accounts.account'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='asset',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_%(class)ss', to='assets.asset'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='execution',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='execution_%(class)ss', to='accounts.automationexecution'),
),
migrations.CreateModel(
name='PushSecretRecord',
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)),
('date_finished', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16, verbose_name='Status')),
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='accounts.account')),
('asset', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_%(class)ss', to='assets.asset')),
('execution', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='execution_%(class)ss', to='accounts.automationexecution')),
],
options={
'verbose_name': 'Push secret record',
},
),
]

View File

@ -9,7 +9,7 @@ from common.db import fields
from common.db.models import JMSBaseModel
from .base import AccountBaseAutomation, ChangeSecretMixin
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ]
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'BaseSecretRecord']
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -30,36 +30,42 @@ class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
return attr_json
class ChangeSecretRecord(JMSBaseModel):
class BaseSecretRecord(JMSBaseModel):
account = models.ForeignKey(
'accounts.Account', on_delete=models.SET_NULL,
null=True, related_name='change_secret_records'
null=True, related_name='%(class)ss'
)
asset = models.ForeignKey(
'assets.Asset', on_delete=models.SET_NULL,
null=True, related_name='asset_change_secret_records'
null=True, related_name='asset_%(class)ss'
)
execution = models.ForeignKey(
'accounts.AutomationExecution', on_delete=models.SET_NULL,
null=True, related_name='execution_change_secret_records',
null=True, related_name='execution_%(class)ss',
)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'), db_index=True)
ignore_fail = models.BooleanField(default=False, verbose_name=_('Ignore fail'))
status = models.CharField(
max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value
)
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
class Meta:
verbose_name = _("Change secret record")
abstract = True
def __str__(self):
return f'{self.account.username}@{self.asset}'
@staticmethod
def get_valid_records():
return ChangeSecretRecord.objects.exclude(
@classmethod
def get_valid_records(cls):
return cls.objects.exclude(
Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
)
class ChangeSecretRecord(BaseSecretRecord):
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
ignore_fail = models.BooleanField(default=False, verbose_name=_('Ignore fail'))
class Meta:
verbose_name = _("Change secret record")

View File

@ -3,10 +3,10 @@ from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
from .base import AccountBaseAutomation, ChangeSecretMixin
from .change_secret import BaseSecretRecord
__all__ = ['PushAccountAutomation']
__all__ = ['PushAccountAutomation', 'PushSecretRecord']
class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -36,3 +36,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
class Meta:
verbose_name = _("Push asset account")
class PushSecretRecord(BaseSecretRecord):
class Meta:
verbose_name = _("Push secret record")