secator 0.18.0__tar.gz → 0.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secator might be problematic. Click here for more details.
- {secator-0.18.0 → secator-0.19.0}/CHANGELOG.md +7 -0
- {secator-0.18.0 → secator-0.19.0}/PKG-INFO +1 -1
- {secator-0.18.0 → secator-0.19.0}/pyproject.toml +1 -1
- {secator-0.18.0 → secator-0.19.0}/secator/config.py +2 -1
- {secator-0.18.0 → secator-0.19.0}/secator/installer.py +1 -1
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/stat.py +8 -3
- {secator-0.18.0 → secator-0.19.0}/secator/runners/_base.py +0 -2
- {secator-0.18.0 → secator-0.19.0}/secator/runners/command.py +131 -13
- {secator-0.18.0 → secator-0.19.0}/.coderabbit.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.alpine +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.arch +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.debian +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.kali +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.osx +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/Dockerfile.ubuntu +0 -0
- {secator-0.18.0 → secator-0.19.0}/.docker/build_all.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/.dockerignore +0 -0
- {secator-0.18.0 → secator-0.19.0}/.flake8 +0 -0
- {secator-0.18.0 → secator-0.19.0}/.gitignore +0 -0
- {secator-0.18.0 → secator-0.19.0}/CONTRIBUTING.md +0 -0
- {secator-0.18.0 → secator-0.19.0}/Dockerfile +0 -0
- {secator-0.18.0 → secator-0.19.0}/LICENSE +0 -0
- {secator-0.18.0 → secator-0.19.0}/README.md +0 -0
- {secator-0.18.0 → secator-0.19.0}/SECURITY.md +0 -0
- {secator-0.18.0 → secator-0.19.0}/cloudbuild.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/.helmignore +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/Chart.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/templates/redis-service.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/templates/redis.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/templates/secator-manager.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/templates/secator-worker.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/helm/values.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/download_cves.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/generate_tools_md_table.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/install.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/install_asciinema.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/install_go.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/install_ruby.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/msf/exploit_cve.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_anonymous.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_version.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/msf/ftp_vsftpd_234_backdoor.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/msf/redis.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/STORY.md +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/aliases.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/demo.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/fmt.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/input.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/pipe.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/stories/short_demo.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/scripts/update_tools.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/.gitignore +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/celery.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/celery_signals.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/celery_utils.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/cli.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/cli_helper.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/click.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/aggressive.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/http_headless.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/http_record.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/insane.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/paranoid.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/polite.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/sneaky.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/profiles/tor.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/domain.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/host.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/network.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/subdomain.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/scans/url.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/cidr_recon.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/code_scan.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/host_recon.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/subdomain_recon.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_bypass.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_crawl.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_dirsearch.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_fuzz.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_params_fuzz.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/url_vuln.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/user_hunt.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/configs/workflows/wordpress.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/cve.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/decorators.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/definitions.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/_base.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/console.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/csv.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/gdrive.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/json.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/table.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/exporters/txt.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/hooks/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/hooks/gcs.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/hooks/mongodb.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/loader.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/_base.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/certificate.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/error.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/exploit.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/info.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/ip.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/port.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/progress.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/record.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/state.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/subdomain.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/tag.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/target.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/url.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/user_account.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/vulnerability.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/output_types/warning.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/report.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/rich.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/_helpers.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/celery.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/scan.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/task.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/runners/workflow.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/scans/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/serializers/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/serializers/dataclass.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/serializers/json.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/serializers/regex.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/_categories.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/arjun.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/bbot.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/bup.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/cariddi.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/dalfox.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/dirsearch.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/dnsx.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/feroxbuster.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/ffuf.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/fping.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/gau.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/gf.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/gitleaks.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/gospider.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/grype.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/h8mail.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/httpx.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/katana.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/maigret.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/mapcidr.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/msfconsole.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/naabu.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/nmap.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/nuclei.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/searchsploit.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/subfinder.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/testssl.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/trivy.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/wafw00f.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/wpprobe.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tasks/wpscan.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/template.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/thread.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/tree.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/utils.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/utils_test.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/secator/workflows/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/fixtures/h8mail_breach.txt +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/fixtures/ls.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/fixtures/msfconsole_input.rc +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/fixtures/nmap_output.xml +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/all.yaml +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/inputs.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/outputs.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/setup.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/teardown.sh +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_addons.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_celery.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_scans.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_tasks.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_tasks_categories.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_worker.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/test_workflows.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/wordlist.txt +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/wordlist_dns.txt +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/wordpress_toolbox/Dockerfile +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/integration/wordpress_toolbox/Makefile +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/performance/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/performance/loadtester.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/performance/test_worker.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/template/test_templates.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/__init__.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_celery.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_cli.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_command.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_config.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_offline.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_runners.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_runners_helpers.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_scans.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_serializers.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_tasks.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_tasks_categories.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_template.py +0 -0
- {secator-0.18.0 → secator-0.19.0}/tests/unit/test_utils.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.19.0](https://github.com/freelabz/secator/compare/v0.18.0...v0.19.0) (2025-10-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add monitor thread ([#727](https://github.com/freelabz/secator/issues/727)) ([5377e77](https://github.com/freelabz/secator/commit/5377e77186c88b1331b7ddc4a5304a610ad3e253))
|
|
9
|
+
|
|
3
10
|
## [0.18.0](https://github.com/freelabz/secator/compare/v0.17.0...v0.18.0) (2025-10-22)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -67,6 +67,8 @@ class Celery(StrictModel):
|
|
|
67
67
|
task_acks_late: bool = False
|
|
68
68
|
task_send_sent_event: bool = False
|
|
69
69
|
task_reject_on_worker_lost: bool = False
|
|
70
|
+
task_max_timeout: int = -1
|
|
71
|
+
task_memory_limit_mb: int = -1
|
|
70
72
|
worker_max_tasks_per_child: int = 20
|
|
71
73
|
worker_prefetch_multiplier: int = 1
|
|
72
74
|
worker_send_task_events: bool = False
|
|
@@ -100,7 +102,6 @@ class Security(StrictModel):
|
|
|
100
102
|
allow_local_file_access: bool = True
|
|
101
103
|
auto_install_commands: bool = True
|
|
102
104
|
force_source_install: bool = False
|
|
103
|
-
memory_limit_mb: int = -1
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
class HTTP(StrictModel):
|
|
@@ -150,7 +150,7 @@ class PackageInstaller:
|
|
|
150
150
|
|
|
151
151
|
# Installer cmd
|
|
152
152
|
cmd = distribution.pm_installer
|
|
153
|
-
if CONFIG.security.
|
|
153
|
+
if CONFIG.security.auto_install_commands and IN_CELERY_WORKER_PROCESS:
|
|
154
154
|
cmd = f'flock /tmp/install.lock {cmd}'
|
|
155
155
|
if getpass.getuser() != 'root':
|
|
156
156
|
cmd = f'sudo {cmd}'
|
|
@@ -11,6 +11,7 @@ class Stat(OutputType):
|
|
|
11
11
|
pid: int
|
|
12
12
|
cpu: int
|
|
13
13
|
memory: int
|
|
14
|
+
memory_limit: int
|
|
14
15
|
net_conns: int = field(default=None, repr=True)
|
|
15
16
|
extra_data: dict = field(default_factory=dict)
|
|
16
17
|
_source: str = field(default='', repr=True, compare=False)
|
|
@@ -26,11 +27,15 @@ class Stat(OutputType):
|
|
|
26
27
|
_sort_by = ('name', 'pid')
|
|
27
28
|
|
|
28
29
|
def __str__(self) -> str:
|
|
29
|
-
return f'{self.name} [pid
|
|
30
|
+
return f'{self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%) ([bold]memory[/]:{self.memory:.2f}MB / {self.memory_limit}MB)' # noqa: E501
|
|
30
31
|
|
|
31
32
|
def __repr__(self) -> str:
|
|
32
|
-
s = rf'[dim yellow3]📊 {self.name}
|
|
33
|
+
s = rf'[dim yellow3]📊 {self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%)'
|
|
34
|
+
s += rf' ([bold]memory[/]:{self.memory:.2f}MB'
|
|
35
|
+
if self.memory_limit != -1:
|
|
36
|
+
s += rf' / {self.memory_limit}MB'
|
|
37
|
+
s += ')'
|
|
33
38
|
if self.net_conns:
|
|
34
|
-
s += rf'
|
|
39
|
+
s += rf' ([bold]connections[/]:{self.net_conns})'
|
|
35
40
|
s += ' [/]'
|
|
36
41
|
return rich_to_ansi(s)
|
|
@@ -788,8 +788,6 @@ class Runner:
|
|
|
788
788
|
continue
|
|
789
789
|
result = hook(self, *args)
|
|
790
790
|
self.debug('hook success', obj={'name': hook_type, 'fun': fun}, sub=sub, verbose='item' in sub) # noqa: E501
|
|
791
|
-
if isinstance(result, Error):
|
|
792
|
-
self.add_result(result, hooks=False)
|
|
793
791
|
except Exception as e:
|
|
794
792
|
self.debug('hook failed', obj={'name': hook_type, 'fun': fun}, sub=sub) # noqa: E501
|
|
795
793
|
error = Error.from_exception(e, message=f'Hook "{fun}" execution failed')
|
|
@@ -2,11 +2,13 @@ import copy
|
|
|
2
2
|
import getpass
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import queue
|
|
5
6
|
import re
|
|
6
7
|
import shlex
|
|
7
8
|
import signal
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
11
|
+
import threading
|
|
10
12
|
|
|
11
13
|
from time import time
|
|
12
14
|
|
|
@@ -178,6 +180,13 @@ class Command(Runner):
|
|
|
178
180
|
# Process
|
|
179
181
|
self.process = None
|
|
180
182
|
|
|
183
|
+
# Monitor thread (lazy initialization)
|
|
184
|
+
self.monitor_thread = None
|
|
185
|
+
self.monitor_stop_event = None
|
|
186
|
+
self.monitor_queue = None
|
|
187
|
+
self.process_start_time = None
|
|
188
|
+
# self.retry_count = 0 # TODO: remove this
|
|
189
|
+
|
|
181
190
|
# Sudo
|
|
182
191
|
self.requires_sudo = False
|
|
183
192
|
|
|
@@ -205,6 +214,13 @@ class Command(Runner):
|
|
|
205
214
|
item_loaders.append(instance_func)
|
|
206
215
|
self.item_loaders = item_loaders
|
|
207
216
|
|
|
217
|
+
def _init_monitor_objects(self):
|
|
218
|
+
"""Initialize monitor thread objects when needed (lazy initialization)."""
|
|
219
|
+
if self.monitor_stop_event is None:
|
|
220
|
+
self.monitor_stop_event = threading.Event()
|
|
221
|
+
if self.monitor_queue is None:
|
|
222
|
+
self.monitor_queue = queue.Queue()
|
|
223
|
+
|
|
208
224
|
def toDict(self):
|
|
209
225
|
res = super().toDict()
|
|
210
226
|
res.update({
|
|
@@ -443,7 +459,7 @@ class Command(Runner):
|
|
|
443
459
|
# Output and results
|
|
444
460
|
self.return_code = 0
|
|
445
461
|
self.killed = False
|
|
446
|
-
self.memory_limit_mb = CONFIG.
|
|
462
|
+
self.memory_limit_mb = CONFIG.celery.task_memory_limit_mb
|
|
447
463
|
|
|
448
464
|
# Run the command using subprocess
|
|
449
465
|
env = os.environ
|
|
@@ -458,6 +474,13 @@ class Command(Runner):
|
|
|
458
474
|
env=env,
|
|
459
475
|
cwd=self.cwd)
|
|
460
476
|
|
|
477
|
+
# Initialize monitor objects and start monitor thread
|
|
478
|
+
self._init_monitor_objects()
|
|
479
|
+
self.process_start_time = time()
|
|
480
|
+
self.monitor_stop_event.clear()
|
|
481
|
+
self.monitor_thread = threading.Thread(target=self._monitor_process, daemon=True)
|
|
482
|
+
self.monitor_thread.start()
|
|
483
|
+
|
|
461
484
|
# If sudo password is provided, send it to stdin
|
|
462
485
|
if sudo_password:
|
|
463
486
|
self.process.stdin.write(f"{sudo_password}\n")
|
|
@@ -469,6 +492,7 @@ class Command(Runner):
|
|
|
469
492
|
if not line:
|
|
470
493
|
break
|
|
471
494
|
yield from self.process_line(line)
|
|
495
|
+
yield from self.process_monitor_queue()
|
|
472
496
|
|
|
473
497
|
# Run hooks after cmd has completed successfully
|
|
474
498
|
result = self.run_hooks('on_cmd_done', sub='end')
|
|
@@ -478,11 +502,6 @@ class Command(Runner):
|
|
|
478
502
|
except FileNotFoundError as e:
|
|
479
503
|
yield from self.handle_file_not_found(e)
|
|
480
504
|
|
|
481
|
-
except MemoryError as e:
|
|
482
|
-
self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
|
|
483
|
-
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
484
|
-
yield Warning(message=f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
|
|
485
|
-
|
|
486
505
|
except BaseException as e:
|
|
487
506
|
self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
|
|
488
507
|
self.stop_process()
|
|
@@ -532,13 +551,16 @@ class Command(Runner):
|
|
|
532
551
|
if self.no_process:
|
|
533
552
|
return
|
|
534
553
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if self.
|
|
554
|
+
def process_monitor_queue(self):
|
|
555
|
+
"""Process and yield any queued items from monitor thread."""
|
|
556
|
+
if self.monitor_queue is None:
|
|
538
557
|
return
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
558
|
+
while not self.monitor_queue.empty():
|
|
559
|
+
try:
|
|
560
|
+
monitor_item = self.monitor_queue.get_nowait()
|
|
561
|
+
yield monitor_item
|
|
562
|
+
except queue.Empty:
|
|
563
|
+
break
|
|
542
564
|
|
|
543
565
|
def print_description(self):
|
|
544
566
|
"""Print description"""
|
|
@@ -585,6 +607,100 @@ class Command(Runner):
|
|
|
585
607
|
if exit_ok:
|
|
586
608
|
self.exit_ok = True
|
|
587
609
|
|
|
610
|
+
def _stop_monitor_thread(self):
|
|
611
|
+
"""Stop monitor thread."""
|
|
612
|
+
if self.monitor_thread and self.monitor_thread.is_alive() and self.monitor_stop_event:
|
|
613
|
+
self.monitor_stop_event.set()
|
|
614
|
+
self.monitor_thread.join(timeout=2.0)
|
|
615
|
+
|
|
616
|
+
def _monitor_process(self):
|
|
617
|
+
"""Monitor thread that checks process health and kills if necessary."""
|
|
618
|
+
last_stats_time = 0
|
|
619
|
+
|
|
620
|
+
while not self.monitor_stop_event.is_set():
|
|
621
|
+
if not self.process or not self.process.pid:
|
|
622
|
+
break
|
|
623
|
+
|
|
624
|
+
try:
|
|
625
|
+
current_time = time()
|
|
626
|
+
self.debug('Collecting monitor items', sub='monitor')
|
|
627
|
+
|
|
628
|
+
# Collect and queue stats at regular intervals
|
|
629
|
+
if (current_time - last_stats_time) >= CONFIG.runners.stat_update_frequency:
|
|
630
|
+
stats_items = list(self._collect_stats())
|
|
631
|
+
for stat_item in stats_items:
|
|
632
|
+
if self.monitor_queue is not None:
|
|
633
|
+
self.monitor_queue.put(stat_item)
|
|
634
|
+
last_stats_time = current_time
|
|
635
|
+
|
|
636
|
+
# Check memory usage from collected stats
|
|
637
|
+
if self.memory_limit_mb and self.memory_limit_mb != -1:
|
|
638
|
+
total_mem = sum(stat_item.extra_data.get('memory_info', {}).get('rss', 0) / 1024 / 1024 for stat_item in stats_items) # noqa: E501
|
|
639
|
+
if total_mem > self.memory_limit_mb:
|
|
640
|
+
warning = Warning(message=f'Memory limit {self.memory_limit_mb}MB exceeded (actual: {total_mem:.2f}MB)')
|
|
641
|
+
if self.monitor_queue is not None:
|
|
642
|
+
self.monitor_queue.put(warning)
|
|
643
|
+
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
644
|
+
break
|
|
645
|
+
|
|
646
|
+
# Check execution time
|
|
647
|
+
if self.process_start_time and CONFIG.celery.task_max_timeout != -1:
|
|
648
|
+
elapsed_time = current_time - self.process_start_time
|
|
649
|
+
if elapsed_time > CONFIG.celery.task_max_timeout:
|
|
650
|
+
warning = Warning(message=f'Task timeout {CONFIG.celery.task_max_timeout}s exceeded')
|
|
651
|
+
if self.monitor_queue is not None:
|
|
652
|
+
self.monitor_queue.put(warning)
|
|
653
|
+
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
654
|
+
break
|
|
655
|
+
|
|
656
|
+
# Check retry count
|
|
657
|
+
# TODO: remove this
|
|
658
|
+
# if CONFIG.celery.task_max_retries and self.retry_count >= CONFIG.celery.task_max_retries:
|
|
659
|
+
# warning = Warning(message=f'Max retries {CONFIG.celery.task_max_retries} exceeded (actual: {self.retry_count})')
|
|
660
|
+
# self.monitor_queue.put(warning)
|
|
661
|
+
# self.stop_process(exit_ok=False, sig=signal.SIGTERM)
|
|
662
|
+
# break
|
|
663
|
+
|
|
664
|
+
except Exception as e:
|
|
665
|
+
self.debug(f'Monitor thread error: {e}', sub='monitor')
|
|
666
|
+
warning = Warning(message=f'Monitor thread error: {e}')
|
|
667
|
+
if self.monitor_queue is not None:
|
|
668
|
+
self.monitor_queue.put(warning)
|
|
669
|
+
break
|
|
670
|
+
|
|
671
|
+
# Sleep for a short interval before next check (stat update frequency)
|
|
672
|
+
self.monitor_stop_event.wait(CONFIG.runners.stat_update_frequency)
|
|
673
|
+
|
|
674
|
+
def _collect_stats(self):
|
|
675
|
+
"""Collect stats about the current running process, if any."""
|
|
676
|
+
if not self.process or not self.process.pid:
|
|
677
|
+
return
|
|
678
|
+
proc = psutil.Process(self.process.pid)
|
|
679
|
+
stats = Command.get_process_info(proc, children=True)
|
|
680
|
+
total_mem = 0
|
|
681
|
+
for info in stats:
|
|
682
|
+
name = info['name']
|
|
683
|
+
pid = info['pid']
|
|
684
|
+
cpu_percent = info['cpu_percent']
|
|
685
|
+
# mem_percent = info['memory_percent']
|
|
686
|
+
mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
|
|
687
|
+
total_mem += mem_rss
|
|
688
|
+
self.debug(f'{name} {pid} {mem_rss}MB', sub='monitor')
|
|
689
|
+
net_conns = info.get('net_connections') or []
|
|
690
|
+
extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
|
|
691
|
+
yield Stat(
|
|
692
|
+
name=name,
|
|
693
|
+
pid=pid,
|
|
694
|
+
cpu=cpu_percent,
|
|
695
|
+
memory=mem_rss,
|
|
696
|
+
memory_limit=self.memory_limit_mb,
|
|
697
|
+
net_conns=len(net_conns),
|
|
698
|
+
extra_data=extra_data
|
|
699
|
+
)
|
|
700
|
+
# self.debug(f'Total mem: {total_mem}MB, memory limit: {self.memory_limit_mb}', sub='monitor')
|
|
701
|
+
# if self.memory_limit_mb and self.memory_limit_mb != -1 and total_mem > self.memory_limit_mb:
|
|
702
|
+
# raise MemoryError(f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
|
|
703
|
+
|
|
588
704
|
def stats(self, memory_limit_mb=None):
|
|
589
705
|
"""Gather stats about the current running process, if any."""
|
|
590
706
|
if not self.process or not self.process.pid:
|
|
@@ -599,7 +715,7 @@ class Command(Runner):
|
|
|
599
715
|
mem_percent = info['memory_percent']
|
|
600
716
|
mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
|
|
601
717
|
total_mem += mem_rss
|
|
602
|
-
self.debug(f'{name} {pid} {mem_rss}MB', sub='stats')
|
|
718
|
+
self.debug(f'process: {name} pid: {pid} memory: {mem_rss}MB', sub='stats')
|
|
603
719
|
net_conns = info.get('net_connections') or []
|
|
604
720
|
extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
|
|
605
721
|
yield Stat(
|
|
@@ -701,6 +817,8 @@ class Command(Runner):
|
|
|
701
817
|
|
|
702
818
|
def _wait_for_end(self):
|
|
703
819
|
"""Wait for process to finish and process output and return code."""
|
|
820
|
+
self._stop_monitor_thread()
|
|
821
|
+
yield from self.process_monitor_queue()
|
|
704
822
|
if not self.process:
|
|
705
823
|
return
|
|
706
824
|
for line in self.process.stdout.readlines():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|