From ae2f2a84b620a94fd1cdf7bab1596c3765d5a61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Tue, 31 Jan 2023 12:06:06 -0300 Subject: [PATCH] Aprimora log dos JOBS --- sigi/apps/casas/jobs/daily/usuario_contato.py | 54 ++++- .../contatos/jobs/monthly/atualiza_ibge.py | 92 +++----- .../convenios/jobs/daily/importa_gescon.py | 28 +-- .../servicos/jobs/daily/sincroniza_dns.py | 112 +++++---- .../servicos/jobs/daily/sincroniza_rancher.py | 215 +++++++----------- .../servicos/emails/report_sincroniza_dns.rst | 30 ++- .../emails/report_sincroniza_rancher.rst | 11 +- .../migrations/0012_add_user_interlegis.py | 34 +++ sigi/apps/utils/mixins.py | 112 +++++++++ sigi/templates/emails/base_email.rst | 1 + sigi/templates/emails/base_report.rst | 17 ++ sigi/templates/emails/report_error.rst | 3 +- 12 files changed, 402 insertions(+), 307 deletions(-) create mode 100644 sigi/apps/servidores/migrations/0012_add_user_interlegis.py create mode 100644 sigi/templates/emails/base_report.rst diff --git a/sigi/apps/casas/jobs/daily/usuario_contato.py b/sigi/apps/casas/jobs/daily/usuario_contato.py index a72dfe0..0da0335 100644 --- a/sigi/apps/casas/jobs/daily/usuario_contato.py +++ b/sigi/apps/casas/jobs/daily/usuario_contato.py @@ -1,13 +1,21 @@ from django.db.models import Q from django.contrib.auth.models import User +from django.utils.translation import gettext as _ from django_extensions.management.jobs import DailyJob -from sigi.apps.casas.models import Orgao, Funcionario +from sigi.apps.casas.models import Funcionario +from sigi.apps.servidores.models import Servidor +from sigi.apps.utils.mixins import JobReportMixin -class Job(DailyJob): +class Job(JobReportMixin, DailyJob): help = "Ativa / desativa usuários para os Contatos Interlegis" - def execute(self): + def do_job(self): + self.report_data = [] + tot_news = 0 + tot_updates = 0 + tot_deactivated = 0 + # Seleciona contatos interlegis com mínimo de informações # (nome, cpf, email). Elegíveis para fazer login no sistema contatos = Funcionario.objects.filter( @@ -29,13 +37,37 @@ class Job(DailyJob): }, username=email, ) - print( - f"{['Updated', 'Created'][created]} user {user.username} for contato {contato.id}" + if created: + tot_news += 1 + self.admin_log_addition(user, "Novo contato técnico habilitado") + else: + tot_updates += 1 + self.admin_log_change(user, "Contato técnico atualizado") + + self.report_data.append( + _( + f"Usuário '{user.username}' " + f"{['atualizado', 'criado'][created]} " + f"para o contato {contato.id}" + ) ) + # Desativa usuários de contatos que não estão na lista de elegíveis - qtd_desativados = ( - User.objects.filter(username__contains="@", is_active=True) - .exclude(username__in=contatos.values_list("email", flat=True)) - .update(is_active=False) - ) - print(f"{qtd_desativados} usuários desativados") + for user in User.objects.filter( + username__contains="@", is_active=True + ).exclude(username__in=contatos.values_list("email", flat=True)): + user.is_active = False + user.save() + self.admin_log_change( + user, + _("Desativado pelo sistema - Não é mais contato técnico"), + ) + tot_deactivated += 1 + + self.report_data.append("") + self.report_data.append(_("RESUMO")) + self.report_data.append("------") + self.report_data.append("") + self.report_data.append(_(f"{tot_news} novos usuários")) + self.report_data.append(_(f"{tot_updates} usuários atualizados")) + self.report_data.append(_(f"{tot_deactivated} usuários desativados")) diff --git a/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py b/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py index fced288..7d1b7e0 100644 --- a/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py +++ b/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py @@ -1,7 +1,6 @@ -import docutils.core -from datetime import datetime from django_extensions.management.jobs import MonthlyJob from django.conf import settings +from django.contrib.admin.models import ADDITION, CHANGE from django.core.mail import mail_admins from django.template.loader import render_to_string from django.utils.translation import gettext as _ @@ -12,13 +11,16 @@ from sigi.apps.contatos.models import ( Microrregiao, Municipio, ) +from sigi.apps.servidores.models import Servidor +from sigi.apps.utils.mixins import JobReportMixin -class Job(MonthlyJob): +class Job(JobReportMixin, MonthlyJob): help = _( "Atualiza Unidades Federativas, mesorregiões, microrregiões e " "municípios com dados do IBGE" ) + report_template = "contatos/emails/report_atualiza_ibge.rst" uf_novas = [] uf_atualizadas = [] @@ -28,25 +30,27 @@ class Job(MonthlyJob): meso_atualizadas = [] micro_novas = [] micro_atualizadas = [] + sigi_user = None - def execute(self): - print( - "Atualizando dados do IBGE. " - f"Início: {datetime.now(): %d/%m/%Y %H:%M:%S}" - ) + def do_job(self): self.atualiza_ufs() self.atualiza_municipios() - self.report() - print(f"Término: {datetime.now(): %d/%m/%Y %H:%M:%S}") + + self.report_data = { + "uf_novas": self.uf_novas, + "uf_atualizadas": self.uf_atualizadas, + "municipios_novos": self.municipios_novos, + "municipios_atualizados": self.municipios_atualizados, + "meso_novas": self.meso_novas, + "meso_atualizadas": self.meso_atualizadas, + "micro_novas": self.micro_novas, + "micro_atualizadas": self.micro_atualizadas, + } def atualiza_ufs(self): - ibge_ufs = Estados().json() regioes_map = {"N": "NO", "NE": "NE", "SE": "SE", "S": "SL", "CO": "CO"} - self.uf_novas = [] - self.uf_atualizadas = [] - - for ibge_uf in ibge_ufs: + for ibge_uf in Estados().json(): regiao = regioes_map[ibge_uf["regiao"]["sigla"]] try: sigi_uf = UnidadeFederativa.objects.get( @@ -62,6 +66,7 @@ class Job(MonthlyJob): ) sigi_uf.save() self.uf_novas.append(sigi_uf) + self.admin_log_addition(sigi_uf, "Nova UF encontrada no IBGE") if ( sigi_uf.nome != ibge_uf["nome"] or sigi_uf.sigla != ibge_uf["sigla"] @@ -72,19 +77,10 @@ class Job(MonthlyJob): sigi_uf.regiao = regiao sigi_uf.save() self.uf_atualizadas.append(sigi_uf) + self.admin_log_change(sigi_uf, "Atualizada pelo IBGE") def atualiza_municipios(self): - - ibge_municipios = Municipios().json() - - self.municipios_novos = [] - self.municipios_atualizados = [] - self.meso_novas = [] - self.meso_atualizadas = [] - self.micro_novas = [] - self.micro_atualizadas = [] - - for ibge_mun in ibge_municipios: + for ibge_mun in Municipios().json(): uf_id = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["id"] cod_meso = ibge_mun["microrregiao"]["mesorregiao"]["id"] cod_micro = int( @@ -101,10 +97,14 @@ class Job(MonthlyJob): ) meso.save() self.meso_novas.append(meso) + self.admin_log_addition( + meso, "Nova mesorregião encontrada no IBGE" + ) if meso.nome != ibge_mun["microrregiao"]["mesorregiao"]["nome"]: meso.nome = ibge_mun["microrregiao"]["mesorregiao"]["nome"] meso.save() self.meso_atualizadas.append(meso) + self.admin_log_change(meso, "Atualizada pelo IBGE") # Atualiza ou cria a microrregião # try: micro = Microrregiao.objects.get(codigo_ibge=cod_micro) @@ -115,6 +115,10 @@ class Job(MonthlyJob): nome=ibge_mun["microrregiao"]["nome"], ) micro.save() + self.micro_novas.append(micro) + self.admin_log_addition( + micro, "Nova microrregião encontrada no IBGE" + ) if ( micro.nome != ibge_mun["microrregiao"]["nome"] or micro.mesorregiao != meso @@ -123,6 +127,7 @@ class Job(MonthlyJob): micro.mesorregiao = meso micro.save() self.micro_atualizadas.append(micro) + self.admin_log_change(micro, "Atualizada pelo IBGE") # Atualiza ou cria o município # try: sigi_mun = Municipio.objects.get(codigo_ibge=ibge_mun["id"]) @@ -137,6 +142,9 @@ class Job(MonthlyJob): ) sigi_mun.save() self.municipios_novos.append(sigi_mun) + self.admin_log_addition( + sigi_mun, "Novo município encontrado no IBGE" + ) if ( sigi_mun.nome != ibge_mun["nome"] or sigi_mun.uf_id != uf_id @@ -147,34 +155,4 @@ class Job(MonthlyJob): sigi_mun.microrregiao = micro sigi_mun.save() self.municipios_atualizados.append(sigi_mun) - - def report(self): - rst = render_to_string( - "contatos/emails/report_atualiza_ibge.rst", - { - "title": self.help, - "uf_novas": self.uf_novas, - "uf_atualizadas": self.uf_atualizadas, - "municipios_novos": self.municipios_novos, - "municipios_atualizados": self.municipios_atualizados, - "meso_novas": self.meso_novas, - "meso_atualizadas": self.meso_atualizadas, - "micro_novas": self.micro_novas, - "micro_atualizadas": self.micro_atualizadas, - }, - ) - html = docutils.core.publish_string( - rst, - writer_name="html5", - settings_overrides={ - "input_encoding": "unicode", - "output_encoding": "unicode", - }, - ) - mail_admins( - subject=self.help, - message=rst, - html_message=html, - fail_silently=True, - ) - print(rst) + self.admin_log_change(sigi_mun, "Atualizada pelo IBGE") diff --git a/sigi/apps/convenios/jobs/daily/importa_gescon.py b/sigi/apps/convenios/jobs/daily/importa_gescon.py index 7ef7691..ba51068 100644 --- a/sigi/apps/convenios/jobs/daily/importa_gescon.py +++ b/sigi/apps/convenios/jobs/daily/importa_gescon.py @@ -4,33 +4,13 @@ from django.core.mail import mail_admins from django.utils.translation import gettext as _ from django_extensions.management.jobs import DailyJob from sigi.apps.convenios.models import Gescon +from sigi.apps.utils.mixins import JobReportMixin -class Job(DailyJob): +class Job(JobReportMixin, DailyJob): help = "Carga de dados do Gescon." - def execute(self): - now = datetime.datetime.now() - print(f"Import gescon data started at {now:%Y-%m-%d %H:%M:%S}") + def do_job(self): gescon = Gescon.load() gescon.importa_contratos() - self.report(gescon, now) - now = datetime.datetime.now() - print(f"Import gescon data finished at {now:%Y-%m-%d %H:%M:%S}") - - def report(self, gescon, now): - rst = gescon.ultima_importacao - html = docutils.core.publish_string( - rst, - writer_name="html5", - settings_overrides={ - "input_encoding": "unicode", - "output_encoding": "unicode", - }, - ) - mail_admins( - subject=_(f"Importação do GESCON em {now:%d/%m/%Y às %Hh%M}"), - message=rst, - html_message=html, - fail_silently=True, - ) + self.report_data = gescon.ultima_importacao.splitlines() diff --git a/sigi/apps/servicos/jobs/daily/sincroniza_dns.py b/sigi/apps/servicos/jobs/daily/sincroniza_dns.py index e9a59ab..ab4fddb 100644 --- a/sigi/apps/servicos/jobs/daily/sincroniza_dns.py +++ b/sigi/apps/servicos/jobs/daily/sincroniza_dns.py @@ -12,6 +12,7 @@ from sigi.apps.servicos import generate_instance_name from sigi.apps.servicos.models import Servico, TipoServico from sigi.apps.casas.models import Orgao from sigi.apps.contatos.models import UnidadeFederativa +from sigi.apps.utils.mixins import JobReportMixin LOG_GERAL = _("Mensagens gerais") IGNORES = ["_psl", "k8s", "www.", "sapl."] @@ -35,33 +36,30 @@ def get_log_entry(): } -class Job(DailyJob): +class Job(JobReportMixin, DailyJob): help = _("Sincronização dos registros de DNS da infraestrutura") - _nomes_gerados = None - _log = {} + report_template = "servicos/emails/report_sincroniza_dns.rst" + nomes_gerados = None + report_data = {} - def execute(self): - print( - _( - "Sincroniza os registros de domínio a partir do DNS." - f" Início: {datetime.datetime.now(): %d/%m/%Y %H:%M:%S}" - ) - ) + def do_job(self): # TODO: Resolver - print("Este CRON está desativado até resolvermos questões internas") - return + raise Exception( + "Este CRON está desativado até resolvermos questões internas" + ) - self._log[LOG_GERAL] = get_log_entry() + self.report_data[LOG_GERAL] = get_log_entry() + self.report_data["reativados"] = get_log_entry() # TODO: Remover + self.info("id,nome do orgao,instancia,tipo,url", "reativados") if ( not settings.REGISTRO_PATH.exists() or not settings.REGISTRO_PATH.is_dir() ): self.error(_(f"Arquivos de DNS não encontrados.")) - self.report() return - self._nomes_gerados = { + self.nomes_gerados = { generate_instance_name(o): o for o in Orgao.objects.filter(tipo__legislativo=True) } @@ -71,7 +69,7 @@ class Job(DailyJob): ).update(flag_confirmado=False) for uf in UnidadeFederativa.objects.all(): - self._log[uf] = get_log_entry() + self.report_data[uf] = get_log_entry() self.processa_uf(uf) self.processa_zones() @@ -80,15 +78,11 @@ class Job(DailyJob): try: shutil.rmtree(settings.REGISTRO_PATH) except Exception as e: - print(_(f"Erro ao excluir diretório {settings.REGISTRO_PATH}")) - - print("Relatório final:\n================") - self.report() - print(_(f" Término: {datetime.datetime.now():%H:%M:%S}.")) + self.info(_(f"Erro ao excluir diretório {settings.REGISTRO_PATH}")) def processa_rec(self, dns_rec, log_entry=LOG_GERAL): dominio = dns_rec["name"][:-1] - nivel = len(dominio.split(".")) + nivel = dominio.count(".") + 1 iname = get_iname(dominio) sigla_srv = get_sigla_serv(dominio) @@ -104,6 +98,25 @@ class Job(DailyJob): # Ignorar esses registros sem fazer log # return + apps = [] + if "rrsets" in dns_rec: + apps = [ + r["name"].split(".")[0] + for r in dns_rec["rrsets"] + if r["type"] != "TXT" and r["name"][:-1].count(".") + 1 > nivel + ] + else: + detail_file = settings.REGISTRO_PATH / f"{dominio}." + if detail_file.exists() and detail_file.is_file(): + detail_data = json.loads(detail_file.read_text()) + if "rrsets" in detail_data: + apps = [ + r["name"].split(".")[0] + for r in detail_data["rrsets"] + if r["type"] != "TXT" + and r["name"][:-1].count(".") + 1 > nivel + ] + try: tipo = TipoServico.objects.get(sigla=sigla_srv, modo="R") except TipoServico.DoesNotExist: @@ -146,6 +159,7 @@ class Job(DailyJob): tipo_servico=tipo, url=dominio, data_desativacao=None ).first() if servico is not None: + servico.instancia = iname self.log_update(servico) else: # Tenta encontrar um registro desativado com mesmo domínio # @@ -153,13 +167,14 @@ class Job(DailyJob): tipo_servico=tipo, url=dominio ).first() if servico is not None: + servico.data_desativacao = None servico.instancia = iname self.log_reativa(servico) if servico is None: # Tenta criar o registro # - if iname in self._nomes_gerados: - orgao = self._nomes_gerados[iname] + if iname in self.nomes_gerados: + orgao = self.nomes_gerados[iname] log_entry = orgao.municipio.uf servico = Servico( casa_legislativa=orgao, @@ -182,6 +197,7 @@ class Job(DailyJob): # atualiza o serviço no SIGI servico.url = dominio servico.instancia = iname + servico.apps = "\n".join(apps) servico.hospedagem_interlegis = True servico.data_verificacao = timezone.localtime() servico.resultado_verificacao = "F" # Funcionando @@ -195,7 +211,7 @@ class Job(DailyJob): return registros = json.loads(file_path.read_text())["rrsets"] - self._log[uf]["sumario"]["total"] = len(registros) + self.report_data[uf]["sumario"]["total"] = len(registros) # Atualiza registros existentes e cria novos # for rec in registros: @@ -239,12 +255,11 @@ class Job(DailyJob): def processa_files(self): file_list = list(settings.REGISTRO_PATH.iterdir()) - self._log[LOG_GERAL]["sumario"]["total"] = len(file_list) + self.report_data[LOG_GERAL]["sumario"]["total"] = len(file_list) for file_path in file_list: if not file_path.is_file(): - self._log[LOG_GERAL]["sumario"]["total"] -= 1 + self.report_data[LOG_GERAL]["sumario"]["total"] -= 1 continue - print(file_path) data = json.loads(file_path.read_text()) self.processa_rec(data) file_path.unlink() @@ -262,36 +277,11 @@ class Job(DailyJob): servico.save() self.log_remove(servico) - def report(self): - rst = render_to_string( - "servicos/emails/report_sincroniza_dns.rst", - { - "log": self._log, - "title": _("Resultado da sincronização do SIGI com o DNS"), - }, - ) - - html = docutils.core.publish_string( - rst, - writer_name="html5", - settings_overrides={ - "input_encoding": "unicode", - "output_encoding": "unicode", - }, - ) - mail_admins( - subject=self.help, - message=rst, - html_message=html, - fail_silently=True, - ) - print(rst) - def error(self, message, log_entry=LOG_GERAL): - self._log[log_entry]["erros"].append(message) + self.report_data[log_entry]["erros"].append(message) def info(self, message, log_entry=LOG_GERAL): - self._log[log_entry]["infos"].append(message) + self.report_data[log_entry]["infos"].append(message) def log_novo(self, srv): orgao = srv.casa_legislativa @@ -301,15 +291,15 @@ class Job(DailyJob): f"para {orgao.nome} ({uf.sigla})" ) self.info(msg, uf) - self._log[uf]["sumario"]["novos"] += 1 + self.report_data[uf]["sumario"]["novos"] += 1 def log_ignore(self, dominio, motivo, log_entry=LOG_GERAL): self.error(_(f"Registro {dominio} ignorado pois {motivo}"), log_entry) - self._log[log_entry]["sumario"]["ignorados"] += 1 + self.report_data[log_entry]["sumario"]["ignorados"] += 1 def log_update(self, srv): uf = srv.casa_legislativa.municipio.uf - self._log[uf]["sumario"]["atualizados"] += 1 + self.report_data[uf]["sumario"]["atualizados"] += 1 def log_reativa(self, srv): orgao = srv.casa_legislativa @@ -318,13 +308,15 @@ class Job(DailyJob): f"Instância {srv.instancia} de {srv.tipo_servico.nome} " f"para {orgao.nome} ({uf.sigla}) reativada no SIGI" ) - self._log[uf]["sumario"]["atualizados"] += 1 + self.report_data[uf]["sumario"]["atualizados"] += 1 self.info(msg, uf) + msg = f"{srv.id},{orgao.nome},{srv.instancia},{srv.tipo_servico.nome},{srv.url}" + self.info(msg, "reativados") def log_remove(self, srv): orgao = srv.casa_legislativa uf = orgao.municipio.uf - self._log[uf]["sumario"]["desativados"] += 1 + self.report_data[uf]["sumario"]["desativados"] += 1 self.info( _( f"Registro {srv.tipo_servico.sigla} {srv.instancia} ({srv.url})" diff --git a/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py b/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py index 05a1d59..5969807 100644 --- a/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py +++ b/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py @@ -11,72 +11,49 @@ from django_extensions.management.jobs import DailyJob from sigi.apps.servicos import generate_instance_name from sigi.apps.servicos.models import Servico, TipoServico from sigi.apps.casas.models import Orgao +from sigi.apps.utils.mixins import JobReportMixin -class Job(DailyJob): +class Job(JobReportMixin, DailyJob): help = _("Sincronização dos Serviços SEIT na infraestrutura") - _nomes_gerados = None - _errors = {} - _infos = {} + report_template = "servicos/emails/report_sincroniza_rancher.rst" + nomes_gerados = None + errors = {} + infos = {} + + def do_job(self): + self.nomes_gerados = { + generate_instance_name(o): o + for o in Orgao.objects.filter(tipo__legislativo=True) + } + + for tipo in TipoServico.objects.filter(modo="H").exclude( + tipo_rancher="" + ): + self.process(tipo) - def execute(self): try: - print( - _( - "Sincroniza os serviços SEIT a partir da infraestrutura." - f" Início: {datetime.datetime.now(): %d/%m/%Y %H:%M:%S}" - ) - ) - self._nomes_gerados = { - generate_instance_name(o): o - for o in Orgao.objects.filter(tipo__legislativo=True) - } - print( - _( - f"\t{len(self._nomes_gerados)} órgãos que podem ter instâncias." - ) - ) - - for tipo in TipoServico.objects.filter(modo="H").exclude( - tipo_rancher="" - ): - print( - _( - f"\tProcessando {tipo.nome}." - f" Início: {datetime.datetime.now():%H:%M:%S}." - ), - end="", - ) - self.process(tipo) - print(_(f" Término: {datetime.datetime.now():%H:%M:%S}.")) - - try: - shutil.rmtree(settings.HOSPEDAGEM_PATH) - except Exception as e: - print( - _(f"Erro ao excluir diretório {settings.HOSPEDAGEM_PATH}") - ) - - print("Relatório final:\n================") - self.report() - - print(_(f"Término: {datetime.datetime.now(): %d/%m/%Y %H:%M:%S}")) + shutil.rmtree(settings.HOSPEDAGEM_PATH) except Exception as e: - self.report_error(e) + pass + + self.report_data = { + "erros": self.errors, + "infos": self.infos, + } def process(self, tipo): self.nomeia_instancias(tipo) NAO_CONSTA = "*não-consta-no-rancher*" - self._errors[tipo] = [] - self._infos[tipo] = [] + self.errors[tipo] = [] + self.infos[tipo] = [] file_path = settings.HOSPEDAGEM_PATH / tipo.arquivo_rancher if not file_path.exists() or not file_path.is_file(): - self._errors[tipo].append(_(f"Arquivo {file_path} não encontado.")) + self.errors[tipo].append(_(f"Arquivo {file_path} não encontado.")) return - with open(file_path, "r") as f: - json_data = json.load(f) + json_data = json.loads(file_path.read_text()) portais = [ item @@ -88,7 +65,7 @@ class Job(DailyJob): novos = 0 desativados = 0 - self._infos[tipo].append( + self.infos[tipo].append( _(f"{len(portais)} {tipo.nome} encontrados no Rancher") ) @@ -104,7 +81,7 @@ class Job(DailyJob): hostname = p["spec"]["values"][tipo.spec_rancher]["domain"] else: hostname = NAO_CONSTA - self._errors[tipo].append( + self.errors[tipo].append( _( f"Instância {iname} de {tipo.nome} sem URL no " "rancher" @@ -120,17 +97,27 @@ class Job(DailyJob): hostname = f"{tipo.prefixo_padrao}.{hostname}" else: hostname = NAO_CONSTA - self._errors[tipo].append( + self.errors[tipo].append( _(f"Instância {iname} de {tipo.nome} sem URL no rancher") ) + nova_versao = ( + p["spec"]["values"]["image"]["tag"] + if "image" in p["spec"]["values"] + else "" + ) + if NAO_CONSTA in hostname: + nova_url = "" + else: + nova_url = f"https://{hostname}/" + try: portal = Servico.objects.get( instancia=iname, tipo_servico=tipo, data_desativacao=None ) encontrados += 1 except Servico.MultipleObjectsReturned: - self._errors[tipo].append( + self.errors[tipo].append( _( f"Existe mais de um registro ativo da instância {iname}" f" de {tipo}." @@ -138,23 +125,28 @@ class Job(DailyJob): ) continue except Servico.DoesNotExist: - if iname in self._nomes_gerados: - orgao = self._nomes_gerados[iname] + if iname in self.nomes_gerados: + orgao = self.nomes_gerados[iname] portal = Servico( casa_legislativa=orgao, tipo_servico=tipo, instancia=iname, + url=nova_url, + versao=nova_versao, data_ativacao=p["spec"]["info"]["firstDeployed"][:10], + hospedagem_interlegis=True, ) - self._infos[tipo].append( + portal.save() + self.admin_log_addition(portal, "Criado no Rancher") + novos += 1 + self.infos[tipo].append( _( f"Criada instância {iname} de {tipo.nome} para " f"{orgao.nome} ({orgao.municipio.uf.sigla})" ) ) - novos += 1 else: - self._errors[tipo].append( + self.errors[tipo].append( _( f"{iname} ({hostname}) não parece pertencer a " "nenhum órgão." @@ -162,17 +154,34 @@ class Job(DailyJob): ) continue # atualiza o serviço no SIGI - portal.versao = ( - p["spec"]["values"]["image"]["tag"] - if "image" in p["spec"]["values"] - else "" - ) - if NAO_CONSTA in hostname: - portal.url = "" - else: - portal.url = f"https://{hostname}/" - portal.hospedagem_interlegis = True - portal.save() + if ( + nova_versao != portal.versao + or nova_url != portal.url + or not portal.hospedagem_interlegis + ): + message = ( + "Atualizado no Rancher: " + + ( + f"Versão: de '{portal.versao}' para '{nova_versao}' " + if portal.versao != nova_versao + else "" + ) + + ( + f"Url: de '{portal.url}' para '{nova_url}' " + if portal.url != nova_url + else "" + ) + + ( + f"hospedagem interlegis" + if not portal.hospedagem_interlegis + else "" + ) + ) + portal.versao = nova_versao + portal.url = nova_url + portal.hospedagem_interlegis = True + portal.save() + self.admin_log_change(portal, message) # Desativa portais registrados no SIGI que não estão no Rancher # nomes_instancias = [p["metadata"]["name"] for p in portais] @@ -186,20 +195,19 @@ class Job(DailyJob): portal.data_desativacao = timezone.localdate() portal.motivo_desativacao = _("Não encontrado no Rancher") portal.save() - self._infos[tipo].append( + self.admin_log_change(portal, "Desativado no Rancher") + self.infos[tipo].append( f"{portal.instancia} ({portal.url}) de " f"{portal.casa_legislativa.nome} desativado pois não " "foi encontrado no Rancher." ) desativados += 1 - self._infos[tipo].append( + self.infos[tipo].append( _(f"{encontrados} {tipo.nome} do Rancher encontrados no SIGI") ) - self._infos[tipo].append( - _(f"{novos} novos {tipo.nome} criados no SIGI") - ) - self._infos[tipo].append( + self.infos[tipo].append(_(f"{novos} novos {tipo.nome} criados no SIGI")) + self.infos[tipo].append( _(f"{desativados} {tipo.nome} desativados no SIGI") ) @@ -211,55 +219,4 @@ class Job(DailyJob): ): s.instancia = generate_instance_name(s.casa_legislativa) s.save() - - def report(self): - rst = render_to_string( - "servicos/emails/report_sincroniza_rancher.rst", - { - "erros": self._errors, - "infos": self._infos, - "title": _("Resultado da sincronização do SIGI com o Rancher"), - }, - ) - html = docutils.core.publish_string( - rst, - writer_name="html5", - settings_overrides={ - "input_encoding": "unicode", - "output_encoding": "unicode", - }, - ) - mail_admins( - subject=self.help, - message=rst, - html_message=html, - fail_silently=True, - ) - print(rst) - - def report_error(self, error): - import traceback - - rst = render_to_string( - "emails/report_error.rst", - { - "title": _("Resultado da sincronização do SIGI com o Rancher"), - "process_name": self.help, - "traceback": traceback.format_exception(error), - }, - ) - html = docutils.core.publish_string( - rst, - writer_name="html5", - settings_overrides={ - "input_encoding": "unicode", - "output_encoding": "unicode", - }, - ) - mail_admins( - subject=self.help, - message=rst, - html_message=html, - fail_silently=True, - ) - print(rst) + self.admin_log_change(s, "Adicionado nome automático da instância") diff --git a/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_dns.rst b/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_dns.rst index df0cf0b..279d9af 100644 --- a/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_dns.rst +++ b/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_dns.rst @@ -1,43 +1,39 @@ -{% extends 'emails/base_email.rst' %} +{% extends 'emails/base_report.rst' %} {% load i18n %} {% block content %} - -{% trans "Resultado da sincronização dos dados de serviços de registro do SIGI com os registros encontrados no DNS." %} - -* {% trans "Data/hora de execução" %}: {% now 'SHORT_DATETIME_FORMAT' %} - -{% for uf, dados in log.items %} +{% for uf, dados in report_data.items %} +{% if dados.erros or dados.infos or dados.sumario.total > 0 or dados.sumario.novos > 0 or dados.sumario.atualizados > 0 or dados.sumario.desativados > 0 or dados.sumario.ignorados > 0 %} **{{ uf|upper }}** ========================================= - - **SUMÁRIO:** - +{% if dados.sumario.total > 0 or dados.sumario.novos > 0 or dados.sumario.atualizados > 0 or dados.sumario.desativados > 0 or dados.sumario.ignorados > 0%} + **{% trans "SUMÁRIO" %}:** - Total de registros no DNS: {{ dados.sumario.total }} - Registros criados no SIGI: {{ dados.sumario.novos }} - Registros atualizados no SIGI: {{ dados.sumario.atualizados }} - Registros desativados no SIGI: {{ dados.sumario.desativados }} - Registros do DNS ignorados: {{ dados.sumario.ignorados }} +{% endif %} +{% if dados.erros %} - - **ERROS:** + **{% trans "ERROS" %}:** {% for m in dados.erros %} * {{ m }} - {% empty %} - *{% trans "Nenhum erro encontrado" %}* {% endfor %} +{% endif %} +{% if dados.infos %} - **{% trans "INFORMAÇÕES ADICIONAIS" %}** + **{% trans "INFORMAÇÕES ADICIONAIS" %}:** {% for m in dados.infos %} * {{ m }} - {% empty %} - *{% trans "Nenhuma informação adicional gerada" %}* {% endfor %} +{% endif %} +{% endif %} {% endfor %} {% endblock content %} \ No newline at end of file diff --git a/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_rancher.rst b/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_rancher.rst index 815fce0..9331279 100644 --- a/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_rancher.rst +++ b/sigi/apps/servicos/templates/servicos/emails/report_sincroniza_rancher.rst @@ -1,16 +1,11 @@ -{% extends 'emails/base_email.rst' %} +{% extends 'emails/base_report.rst' %} {% load i18n %} {% block content %} -{% trans "Resultado da sincronização dos dados de serviços do SIGI com as instâncias instaladas no Rancher." %} - -* {% trans "Data/hora de execução" %}: {% now 'SHORT_DATETIME_FORMAT' %} - - **{% trans "ERROS ENCONTRADOS" %}** ===================== -{% for tipo, mensagens in erros.items %} +{% for tipo, mensagens in report_data.erros.items %} **{{ tipo.nome|upper }} - {{ tipo.sigla|upper }}** {% for m in mensagens %} * {{ m }} @@ -22,7 +17,7 @@ **{% trans "INFORMAÇÕES ADICIONAIS" %}** ========================== -{% for tipo, mensagens in infos.items %} +{% for tipo, mensagens in report_data.infos.items %} {{ tipo.nome|upper }} - {{ tipo.sigla|upper }} {% for m in mensagens %} * {{ m }} diff --git a/sigi/apps/servidores/migrations/0012_add_user_interlegis.py b/sigi/apps/servidores/migrations/0012_add_user_interlegis.py new file mode 100644 index 0000000..5e092b3 --- /dev/null +++ b/sigi/apps/servidores/migrations/0012_add_user_interlegis.py @@ -0,0 +1,34 @@ +# Generated by Django 4.1.5 on 2023-01-26 14:35 + +from django.db import migrations +from django.contrib.auth import get_user_model + + +def forwards_migration(apps, schema_editor): + Servidor = apps.get_model("servidores", "Servidor") + User = get_user_model() + + sigi = Servidor.objects.get(sigi=True) + + if sigi.user is not None: + # everything is already fine + return + + try: + usuario = User.objects.get_by_natural_key("interlegis") + except User.DoesNotExist: + usuario = User.objects.create_superuser("interlegis") + + sigi.user_id = usuario.id + sigi.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("servidores", "0011_add_servidor_sigi"), + ] + + operations = [ + migrations.RunPython(forwards_migration, migrations.RunPython.noop), + ] diff --git a/sigi/apps/utils/mixins.py b/sigi/apps/utils/mixins.py index 28dc632..ec31916 100644 --- a/sigi/apps/utils/mixins.py +++ b/sigi/apps/utils/mixins.py @@ -1,13 +1,20 @@ +import datetime +import docutils.core +import traceback from collections import OrderedDict from functools import update_wrapper from django import forms from django.contrib import admin from django.contrib.admin import helpers +from django.contrib.admin.models import LogEntry, ADDITION, CHANGE from django.contrib.admin.options import csrf_protect_m from django.contrib.admin.utils import pretty_name +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied, ImproperlyConfigured +from django.core.mail import mail_admins from django.http import Http404 from django.http.response import HttpResponse, HttpResponseRedirect +from django.template.loader import render_to_string from django.template.response import TemplateResponse from django.urls import path from django.utils.translation import gettext as _, ngettext @@ -19,6 +26,10 @@ from import_export.signals import post_export from sigi.apps.utils import field_label +class MisconfiguredError(Exception): + pass + + class ValueField(Field): def get_value(self, obj): if self.attribute is None: @@ -323,3 +334,104 @@ class ReturnMixin: if self._return_path: return HttpResponseRedirect(self._return_path) return response + + +class JobReportMixin: + error_report_template = "emails/report_error.rst" + report_template = "emails/base_report.rst" + report_data = None + sys_user = None + + def execute(self): + start_time = datetime.datetime.now() + + try: + from sigi.apps.servidores.models import Servidor + + self.sys_user = Servidor.objects.get(sigi=True).user + except Exception: + pass + + try: + self.do_job() + except Exception as e: + self.report_error(e) + return + end_time = datetime.datetime.now() + self.report(start_time, end_time) + + def do_job(self): + raise NotImplementedError("Job needs to implement the 'do_job' method") + + def _admin_log(self, object, action_flag, message=""): + if self.sys_user is None: + return # No admin log + LogEntry.objects.log_action( + user_id=self.sys_user.id, + content_type_id=ContentType.objects.get_for_model(type(object)).pk, + object_id=object.id, + object_repr=str(object), + action_flag=action_flag, + change_message=message, + ) + + def admin_log_addition(self, object, message=""): + self._admin_log(object, ADDITION, message) + + def admin_log_change(self, object, message=""): + self._admin_log(object, CHANGE, message) + + def report_error(self, error): + rst = render_to_string( + self.error_report_template, + { + "title": self.help, + "traceback": traceback.format_exception(error), + }, + ) + html = docutils.core.publish_string( + rst, + writer_name="html5", + settings_overrides={ + "input_encoding": "unicode", + "output_encoding": "unicode", + }, + ) + mail_admins( + subject=self.help, + message=rst, + html_message=html, + fail_silently=True, + ) + print(rst) + + def report(self, start_time, end_time): + if self.report_data is None: + raise MisconfiguredError( + "Job needs to define 'report_data' property" + ) + + rst = render_to_string( + self.report_template, + { + "title": self.help, + "start_time": start_time, + "end_time": end_time, + "report_data": self.report_data, + }, + ) + html = docutils.core.publish_string( + rst, + writer_name="html5", + settings_overrides={ + "input_encoding": "unicode", + "output_encoding": "unicode", + }, + ) + mail_admins( + subject=f"JOB: {self.help}", + message=rst, + html_message=html, + fail_silently=True, + ) + print(rst) diff --git a/sigi/templates/emails/base_email.rst b/sigi/templates/emails/base_email.rst index fc03e92..1f93b82 100644 --- a/sigi/templates/emails/base_email.rst +++ b/sigi/templates/emails/base_email.rst @@ -2,6 +2,7 @@ {% for s in title %}={% endfor %} {{ title }} {% for s in title %}={% endfor %} + {% endblock title %} diff --git a/sigi/templates/emails/base_report.rst b/sigi/templates/emails/base_report.rst new file mode 100644 index 0000000..ad3210c --- /dev/null +++ b/sigi/templates/emails/base_report.rst @@ -0,0 +1,17 @@ +{% extends 'emails/base_email.rst' %} +{% load i18n %} + +{% block title %} + {{ block.super }} +**{% trans "Início:" %} {{ start_time|date:"SHORT_DATETIME_FORMAT" }}** +**{% trans "Término:" %} {{ end_time|date:"SHORT_DATETIME_FORMAT" }}** + +{% endblock %} + +{% block content %} +{% trans "RESULTADO" %} +========= + +{% for row in report_data %}{{ row }} +{% endfor %} +{% endblock content %} \ No newline at end of file diff --git a/sigi/templates/emails/report_error.rst b/sigi/templates/emails/report_error.rst index a01f501..20b3962 100644 --- a/sigi/templates/emails/report_error.rst +++ b/sigi/templates/emails/report_error.rst @@ -2,7 +2,7 @@ {% load i18n %} {% block content %} -{% blocktrans %}UM ERRO OCORREU NO PROCESSO DE IMPORTAÇÃO DE {{ process_name }}{% endblocktrans %} +{% blocktrans %}UM ERRO OCORREU NA EXECUÇÃO DA TAREFA{% endblocktrans %} {% trans "VERIFIQUE O LOG DO SIGI PARA MAIORES DETALHES" %} @@ -11,6 +11,7 @@ ::{% autoescape off %}{% for error_row in traceback %} {{ error_row }} {% endfor %} + {% endautoescape %} {% endblock content %} \ No newline at end of file