squad 1.65__tar.gz → 1.67__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {squad-1.65 → squad-1.67}/CHANGELOG.md +14 -0
- {squad-1.65 → squad-1.67}/Dockerfile +1 -0
- {squad-1.65/squad.egg-info → squad-1.67}/PKG-INFO +1 -1
- {squad-1.65 → squad-1.67}/doc/ci.rst +47 -2
- {squad-1.65 → squad-1.67}/requirements.txt +1 -0
- {squad-1.65 → squad-1.67}/squad/api/ci.py +42 -0
- {squad-1.65 → squad-1.67}/squad/api/urls.py +1 -0
- {squad-1.65 → squad-1.67}/squad/ci/backend/fake.py +3 -0
- {squad-1.65 → squad-1.67}/squad/ci/backend/lava.py +39 -1
- {squad-1.65 → squad-1.67}/squad/ci/backend/null.py +19 -0
- {squad-1.65 → squad-1.67}/squad/ci/backend/tuxsuite.py +95 -2
- squad-1.67/squad/ci/migrations/0029_create_testjob_results_input.py +22 -0
- {squad-1.65 → squad-1.67}/squad/ci/models.py +29 -0
- {squad-1.65 → squad-1.67}/squad/plugins/github.py +6 -5
- squad-1.67/squad/version.py +1 -0
- {squad-1.65 → squad-1.67/squad.egg-info}/PKG-INFO +1 -1
- {squad-1.65 → squad-1.67}/squad.egg-info/SOURCES.txt +1 -0
- {squad-1.65 → squad-1.67}/squad.egg-info/requires.txt +1 -0
- {squad-1.65 → squad-1.67}/test/api/test_ci.py +53 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/test_tuxsuite.py +240 -3
- {squad-1.65 → squad-1.67}/test/ci/test_models.py +12 -0
- {squad-1.65 → squad-1.67}/test/plugins/test_github.py +17 -0
- squad-1.65/squad/version.py +0 -1
- {squad-1.65 → squad-1.67}/.ackrc +0 -0
- {squad-1.65 → squad-1.67}/.coveragerc +0 -0
- {squad-1.65 → squad-1.67}/.ctags +0 -0
- {squad-1.65 → squad-1.67}/.dockerignore +0 -0
- {squad-1.65 → squad-1.67}/.github/workflows/release.yml +0 -0
- {squad-1.65 → squad-1.67}/.github/workflows/test.yml +0 -0
- {squad-1.65 → squad-1.67}/.gitignore +0 -0
- {squad-1.65 → squad-1.67}/.mailmap +0 -0
- {squad-1.65 → squad-1.67}/.readthedocs.yml +0 -0
- {squad-1.65 → squad-1.67}/.reuse/dep5 +0 -0
- {squad-1.65 → squad-1.67}/COPYING +0 -0
- {squad-1.65 → squad-1.67}/COPYRIGHTS +0 -0
- {squad-1.65 → squad-1.67}/LICENSES/GPL-3.0-or-later.txt +0 -0
- {squad-1.65 → squad-1.67}/LICENSES/MIT.txt +0 -0
- {squad-1.65 → squad-1.67}/LICENSES/OFL-1.1.txt +0 -0
- {squad-1.65 → squad-1.67}/MANIFEST.in +0 -0
- {squad-1.65 → squad-1.67}/Procfile +0 -0
- {squad-1.65 → squad-1.67}/README.rst +0 -0
- {squad-1.65 → squad-1.67}/babel.cfg +0 -0
- {squad-1.65 → squad-1.67}/dev-docker +0 -0
- {squad-1.65 → squad-1.67}/doc/.gitignore +0 -0
- {squad-1.65 → squad-1.67}/doc/Makefile +0 -0
- {squad-1.65 → squad-1.67}/doc/api.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/conf.py +0 -0
- {squad-1.65 → squad-1.67}/doc/hacking.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/index.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/install.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/intro.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/lava_usecase.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/plugins.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/quickstart.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/translating.rst +0 -0
- {squad-1.65 → squad-1.67}/doc/tuxsuite_usecase.rst +0 -0
- {squad-1.65 → squad-1.67}/docker-compose.yaml +0 -0
- {squad-1.65 → squad-1.67}/manage.py +0 -0
- {squad-1.65 → squad-1.67}/package-lock.json +0 -0
- {squad-1.65 → squad-1.67}/package.json +0 -0
- {squad-1.65 → squad-1.67}/pytest.ini +0 -0
- {squad-1.65 → squad-1.67}/requirements-dev.txt +0 -0
- {squad-1.65 → squad-1.67}/scripts/build +0 -0
- {squad-1.65 → squad-1.67}/scripts/check-ci +0 -0
- {squad-1.65 → squad-1.67}/scripts/check-ignore +0 -0
- {squad-1.65 → squad-1.67}/scripts/community_connector/main.js +0 -0
- {squad-1.65 → squad-1.67}/scripts/community_connector/manifest.json +0 -0
- {squad-1.65 → squad-1.67}/scripts/dogfood +0 -0
- {squad-1.65 → squad-1.67}/scripts/get-metrics +0 -0
- {squad-1.65 → squad-1.67}/scripts/get-tests +0 -0
- {squad-1.65 → squad-1.67}/scripts/git-build +0 -0
- {squad-1.65 → squad-1.67}/scripts/pytest +0 -0
- {squad-1.65 → squad-1.67}/scripts/rabbitmq-server +0 -0
- {squad-1.65 → squad-1.67}/scripts/release +0 -0
- {squad-1.65 → squad-1.67}/scripts/release-docker +0 -0
- {squad-1.65 → squad-1.67}/scripts/squad-config +0 -0
- {squad-1.65 → squad-1.67}/scripts/test-ci +0 -0
- {squad-1.65 → squad-1.67}/scripts/test-docker +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/gen-ci-jobs +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/gen-metrics +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/gen-test-data +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/gen-tests +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/setup-dev +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/submit-ci-jobs +0 -0
- {squad-1.65 → squad-1.67}/scripts/testdata/submit-test-data +0 -0
- {squad-1.65 → squad-1.67}/scripts/translate +0 -0
- {squad-1.65 → squad-1.67}/scripts/travis-lava +0 -0
- {squad-1.65 → squad-1.67}/scripts/update-translation-files +0 -0
- {squad-1.65 → squad-1.67}/scripts/upload +0 -0
- {squad-1.65 → squad-1.67}/setup.cfg +0 -0
- {squad-1.65 → squad-1.67}/setup.py +0 -0
- {squad-1.65 → squad-1.67}/squad/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/admin.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/apps.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/filters.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/rest.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/utils.py +0 -0
- {squad-1.65 → squad-1.67}/squad/api/views.py +0 -0
- {squad-1.65 → squad-1.67}/squad/celery.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/admin.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/apps.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/backend/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/exceptions.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/management/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/management/commands/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/management/commands/create_tuxsuite_boot_tests.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/management/commands/listen.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/management/commands/testfetch.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0001_initial.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0002_auto_20170406_1252.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0003_backend_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0004_testjob_failure.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0005_remove_listener_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0006_simplify_backend_loading.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0007_auto_20170517_1736.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0008_testjob_testrun.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0009_slug_pattern.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0010_testjob_can_resubmit.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0011_testjob_resubmitted_count.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0012_testjob_build.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0013_testjob_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0014_testjob_target_build.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0015_testjob_populate_target_build.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0016_backend_max_fetch_attempts.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0017_testjob_fetch_attempts.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0018_testjob_dates.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0019_add_fake_backend.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0020_backend_settings_field.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0021_testjob_parent_job.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0022_backend_poll_enabled.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0023_remove_testjob_build.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0024_fix_testjob_environment_validation.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0025_backend_listen_enabled.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0026_job_start_end_time.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0027_add_tuxsuite_implementation_type.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/0028_create_testjob_indexes.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/migrations/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/tasks.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/templates/squad/ci/testjob_resubmit.html.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/templates/squad/ci/testjob_resubmit.txt.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/templatetags/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/templatetags/filter_jobs.py +0 -0
- {squad-1.65 → squad-1.67}/squad/ci/utils.py +0 -0
- {squad-1.65 → squad-1.67}/squad/compat.py +0 -0
- {squad-1.65 → squad-1.67}/squad/container_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/admin.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/apps.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/callback.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/comparison.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/failures.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/history.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/locale/django.pot +0 -0
- {squad-1.65 → squad-1.67}/squad/core/locale/es_MX/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/core/locale/pl/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/core/locale/pt/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/core/locale/pt_BR/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/compute_build_summaries.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/compute_project_statuses.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/fill_test_metadata.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/fix_squadplugin_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/import_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/import_data.rst +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/migrate_test_runs.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/populate_metric_build_and_environment.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/populate_test_build_and_environment.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/prepdump.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/send-email.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/update_project_statuses.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/management/commands/users.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0001_initial.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0002_auto_20160525_1403.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0003_testrun_log_file.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0004_group_user_groups.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0005_token.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0006_auto_20160826_2242.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0007_testrun_data_processed.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0008_status.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0009_testrun_status_recorded.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0010_testrun_datetime.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0011_testrun_metadata_fields.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0012_build_datetime.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0013_testrun_resubmit_url.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0014_testrun_metadata_file.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0015_attachment.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0016_project_is_public.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0017_slug_validator.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0018_build_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0019_build_version.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0020_build_ordering.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0021_global_tokens.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0022_projectstatus.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0023_subscription.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0024_project_build_completion_threshold.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0025_unique_testrun_job_id.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0026_testrun_result_accept_null.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0027_project_notification_strategy.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0028_suite_and_test_name_length.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0029_subscription_email_formats.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0030_remove_project_build_completion_threshold.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0031_environment_expected_test_runs.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0032_testrun_completed.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0033_drop_debversion.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0034_prepare_to_remove_build_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0035_remove_build_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0036_status_tests_skip.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0037_project_status_test_summary_fields.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0038_populate_project_status_cache.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0039_orderings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0040_remove_subscription_html.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0041_projectstatus_notified.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0042_set_projectstatus_notified.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0043_project_status_build.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0044_project_html_mail.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0045_adminsubscription.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0046_projectstatus_last_updated.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0047_populate_projectstatus_last_updated.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0048_moderate_notifications.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0049_projectstatus_plural.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0050_projectstatus_finished.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0051_build_status.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0052_recreate_projectstatus_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0053_remove_projectstatus_previous.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0054_custom_email_template.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0055_emailtemplate_subject.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0056_project_description.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0057_projectstatus_has_metrics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0058_populate_projectstatus_has_metrics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0059_project_important_metadata_keys.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0060_test_log.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0061_project_enabled_plugins_list.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0062_project_allow_empty_enabled_plugin_list.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0063_project_wait_before_notification.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0064_project_notification_timeout.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0065_projectstatus_notified_on_timeout.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0066_environment_description.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0067_accept_blank_suite_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0068_suite_version.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0069_suite_metadata.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0070_create_suite_test_and_metric_metadata.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0071_migrate_old_tokens.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0072_group_description.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0073_auto_20180420_1643.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0074_add_indexes.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0075_update_project_enabled_plugin_list.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0076_patch_builds.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0077_knownissue.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0078_cache_test_run_counts.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0079_init_cache_test_run_counts.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0080_auto_20180810_0047.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0081_status_has_metrics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0082_populate_status_has_metrics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0083_rename_knownissue_environments.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0084_projectstatus_regressions_fixes.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0085_projectstatus_defaults.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0086_xfail.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0087_test_known_issues.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0088_user_subscriptions.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0089_test_has_known_issues.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0090_populate_test_has_known_issues.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0091_notification_delivery_remove_unique_status.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0092_annotation.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0093_historicalemailtemplate.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0094_populatehistoricalemailtemplate.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0095_project_data_retention_days.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0096_build_keep_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0097_build_placeholder.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0098_blank_annotation.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0099_metricthreshold.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0100_metric_is_outlier.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0101_project_project_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0102_projectstatus_null_metric_summary.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0103_populate_project_status.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0104_delayedreport.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0105_delayed_report_error_message.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0106_delayedreport_output_subject.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0107_move_notification_strategy.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0108_add_email_template_validator.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0109_group_member.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0110_move_users_from_django_groups_to_squad_groups.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0111_remove_group_user_groups.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0112_user_namespaces.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0113_group_project_blank_name_and_description.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0114_project_enabled_plugin_list_can_be_blank.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0115_fix_slug_validation.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0116_make_group_membership_unique.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0117_drop_obsolete_token_model.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0118_project_is_archived.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0119_i18n.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0120_buildsummary.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0121_add_password_patchsource.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0122_fix_patchsource_url_and_token.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0123_django_upgrade_missing_migrations.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0124_set_default_expected_test_runs_to_zero.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0125_fix_missing_status_has_metrics_for_testruns.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0126_metricthreshold_environment.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0127_metric_thresholds_migrate_data.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0128_metric_thresholds_remove_proj_col.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0129_projectstatus_nullable_notified_on_timeout.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0130_project_status_baseline_next.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0131_create_squad_auth_group_and_add_users.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0132_attachment_mimetype.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0133_append_project_permissions_to_squad_auth_group.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0134_longer_metric_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0135_add_privileged_access_level.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0136_migrate_submitters_to_privileged.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0137_patchsource_token_null.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0138_metric_unit.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0139_nullable_test_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0140_increase_gerrit_password_length.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0141_remove_test_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0142_add_testrun_file_storage.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0143_attachment_storage.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0144_attachment_data_null.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0145_pluginscratch.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0146_deprecate_testrun_and_attachment_data_fields.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0147_add_build_and_environment_to_test.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0148_remove_legacy_storage_fields.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0149_build_patch_url.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0150_add_new_notification_strategy.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0151_callback.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0152_add_build_patch_notified.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0153_callback_make_response_content_blob.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0154_project_add_force_finishing_builds_field.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0155_add_build_and_environment_to_metric.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0156_nullable_metric_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0157_remove_metric_name.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0158_add_metric_comparison_to_projectstatus.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0159_nullable_metricthreshold_value.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0160_add_project_to_metricthreshold.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0161_add_metricthreshold_perm_to_squad_group.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0162_project_add_build_confidence_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0163_hirtoricalemailtemplate_update.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0164_django_update.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0165_buildsummary_uniqueness.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0166_build_is_release.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0167_add_project_datetime.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/0168_add_group_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/migrations/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/models.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/notification.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/plugins.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/queries.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/statistics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/tasks/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/tasks/exceptions.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/tasks/notification.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/base.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/diff.html.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/diff.txt.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/failed_test_jobs.html.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/failed_test_jobs.txt.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/moderation.html.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templates/squad/notification/moderation.txt.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templatetags/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/templatetags/squad_notification.py +0 -0
- {squad-1.65 → squad-1.67}/squad/core/utils.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/__main__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/admin.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/apps.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/badges.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/build_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/ci.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/comparison.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/extract.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/failures.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/forms.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/group_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/locale/django.pot +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/locale/pl/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/locale/pt/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/locale/pt_BR/LC_MESSAGES/django.po +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/management/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/management/commands/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/management/commands/get_token.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/metrics.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/project_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/queries.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/setup.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/compare.css +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/download +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/download.conf +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/favicon.ico +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/main.css +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/attach_select2.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/build.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/build_compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/build_list.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/charts.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/common.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/config.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/annotation.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/build_compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/build_list_compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/build_release.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/cancel.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/charts.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/fetch.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/filter.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/metricThreshold.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/project_compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/resubmit.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/controllers/testjobs_progress.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/metric.threshold.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/project_compare.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/showHide.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad/table.js +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/static/squad_sign.svg +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/401.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/404.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/django/rest_framework/api.html +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/django/squad/_user_menu.html +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_builds_table.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_env_suite_data.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_metadata.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_pagination.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_permissions.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_project_list.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_regressions_and_fixes.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_results_table.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_results_transitions_filter.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_subscribe.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_results_envbox.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_results_suitebox.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_results_summary.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_results_table.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_run_metric.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_test_run_test.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_unfinished_build.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/_user_menu.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/base.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build-nav.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build_callbacks.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build_metadata.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build_metrics.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/build_settings.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/builds.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/compare.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/compare_builds.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/compare_projects.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/failures.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group-nav.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/advanced.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/base.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/delete.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/index.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/members.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/new_group.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/group_settings/new_project.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/index.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/knownissues.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/login.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/metrics.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project-nav.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/_threshold_table.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/advanced.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/base.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/build_confidence.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/delete.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/environments.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/index.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/project_settings/thresholds.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/test_history.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/test_run.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/test_run_suite_metrics.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/test_run_suite_test_details.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/test_run_suite_tests.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/testjob.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/testjobs.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/testjobs_progress.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/tests-details-nav.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/tests.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/user_settings/api_token.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/user_settings/base.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/user_settings/profile.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/user_settings/projects.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templates/squad/user_settings/subscriptions.jinja2 +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templatetags/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/templatetags/squad.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/tests.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/urls.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/user_settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/utils.py +0 -0
- {squad-1.65 → squad-1.67}/squad/frontend/views.py +0 -0
- {squad-1.65 → squad-1.67}/squad/http.py +0 -0
- {squad-1.65 → squad-1.67}/squad/jinja2.py +0 -0
- {squad-1.65 → squad-1.67}/squad/mail.py +0 -0
- {squad-1.65 → squad-1.67}/squad/manage.py +0 -0
- {squad-1.65 → squad-1.67}/squad/plugins/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/plugins/example.py +0 -0
- {squad-1.65 → squad-1.67}/squad/plugins/gerrit.py +0 -0
- {squad-1.65 → squad-1.67}/squad/plugins/linux_log_parser.py +0 -0
- {squad-1.65 → squad-1.67}/squad/run/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/run/__main__.py +0 -0
- {squad-1.65 → squad-1.67}/squad/run/listener.py +0 -0
- {squad-1.65 → squad-1.67}/squad/run/scheduler.py +0 -0
- {squad-1.65 → squad-1.67}/squad/run/worker.py +0 -0
- {squad-1.65 → squad-1.67}/squad/settings.py +0 -0
- {squad-1.65 → squad-1.67}/squad/socialaccount.py +0 -0
- {squad-1.65 → squad-1.67}/squad/urls.py +0 -0
- {squad-1.65 → squad-1.67}/squad/wsgi.py +0 -0
- {squad-1.65 → squad-1.67}/squad.egg-info/dependency_links.txt +0 -0
- {squad-1.65 → squad-1.67}/squad.egg-info/entry_points.txt +0 -0
- {squad-1.65 → squad-1.67}/squad.egg-info/top_level.txt +0 -0
- {squad-1.65 → squad-1.67}/squad.svg +0 -0
- {squad-1.65 → squad-1.67}/test/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/api/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/api/benchmarks.csv +0 -0
- {squad-1.65 → squad-1.67}/test/api/benchmarks.json +0 -0
- {squad-1.65 → squad-1.67}/test/api/definition.yaml +0 -0
- {squad-1.65 → squad-1.67}/test/api/metadata.json +0 -0
- {squad-1.65 → squad-1.67}/test/api/test_data.py +0 -0
- {squad-1.65 → squad-1.67}/test/api/test_rest.py +0 -0
- {squad-1.65 → squad-1.67}/test/api/test_run.log +0 -0
- {squad-1.65 → squad-1.67}/test/api/tests.csv +0 -0
- {squad-1.65 → squad-1.67}/test/api/tests.json +0 -0
- {squad-1.65 → squad-1.67}/test/api/tests.py +0 -0
- {squad-1.65 → squad-1.67}/test/api/tests_log.json +0 -0
- {squad-1.65 → squad-1.67}/test/api/tests_two.json +0 -0
- {squad-1.65 → squad-1.67}/test/api/twoline_definition.yaml +0 -0
- {squad-1.65 → squad-1.67}/test/ci/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/example-broken-log.yaml +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/example-lava-log.yaml +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/lava.json +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/test_fake.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/test_lava.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/test_real_lava.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/tuxsuite_test_failed_result_sample.json +0 -0
- {squad-1.65 → squad-1.67}/test/ci/backend/tuxsuite_test_result_sample.json +0 -0
- {squad-1.65 → squad-1.67}/test/ci/test_listen.py +0 -0
- {squad-1.65 → squad-1.67}/test/ci/test_tasks.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_attachment.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_build.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_build_summary.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_callback.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_emailtemplate.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_failures.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_group.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_historical_emailtemplate.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_history.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/1/default/1/metadata.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/1/default/1/metrics.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/2/default/2/metadata.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/2/default/2/metrics.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/2/default/2/screenshot.png +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_input/2/default/2/tests.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_import_data_missing_metadata/1/1/tests.json +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_known_issues.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_metric.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_metric_comparison.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_metric_threshold.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_metrics_data.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_metrics_summary.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_notification.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_notification_delivery.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_patch_source.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_project.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_project_status.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_statistics.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_tasks.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_tasks_notification.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_test.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_test_comparison.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_test_data.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_test_run.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_test_summary.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_update_project_statuses.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_user_namespace.py +0 -0
- {squad-1.65 → squad-1.67}/test/core/test_utils.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_basics.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_comparison.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_get_token_command.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_group_settings.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_history.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_template_tags.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_test_job.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_tests.py +0 -0
- {squad-1.65 → squad-1.67}/test/frontend/test_utils.py +0 -0
- {squad-1.65 → squad-1.67}/test/integration/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/integration/plugins/test_tradefed.py +0 -0
- {squad-1.65 → squad-1.67}/test/integration/plugins/tradefed-output-20220608105250.tar.xz +0 -0
- {squad-1.65 → squad-1.67}/test/integration/test_build_notification_from_ci.py +0 -0
- {squad-1.65 → squad-1.67}/test/javascript.py +0 -0
- {squad-1.65 → squad-1.67}/test/karma.conf.js +0 -0
- {squad-1.65 → squad-1.67}/test/mock.py +0 -0
- {squad-1.65 → squad-1.67}/test/performance.py +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/__init__.py +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/kasan.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/kernel_bug_and_invalid_opcode.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/kernelexceptiontrace.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/kernelpanic.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/kfence.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/multiple_issues_dmesg.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/oops.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/linux_log_parser/rcu_warning.log +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/test_gerrit.py +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/test_linux_log_parser.py +0 -0
- {squad-1.65 → squad-1.67}/test/plugins/test_plugin.py +0 -0
- {squad-1.65 → squad-1.67}/test/settings.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_architecture.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_code_quality.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_cors.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_i18n.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_mail.py +0 -0
- {squad-1.65 → squad-1.67}/test/test_pending_migrations.py +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_annotation.js +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_cancel.js +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_charts.js +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_compare.js +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_filter.js +0 -0
- {squad-1.65 → squad-1.67}/test/unit/test_resubmit.js +0 -0
@@ -1,3 +1,17 @@
|
|
1
|
+
# 1.67
|
2
|
+
|
3
|
+
This 1.77 release adds support for receiving callback events from Tuxsuite
|
4
|
+
backends. This will allow build and test objects to be pushed directly to
|
5
|
+
SQUAD which in turn will stop polling from Tuxsuite, saving unecessary
|
6
|
+
requests. The release also fixes a bug when composing URLs used in github
|
7
|
+
plugin.
|
8
|
+
|
9
|
+
# 1.66
|
10
|
+
|
11
|
+
This 1.66 release adds support for listening to LAVA websockets over
|
12
|
+
ZMQ. The release also fixes infinite Tuxsuite polling when jobs run into
|
13
|
+
infrastructure errors.
|
14
|
+
|
1
15
|
# 1.65
|
2
16
|
|
3
17
|
This 1.65 release adds support to cancelling tuxsuite jobs via SQUAD
|
@@ -225,8 +225,8 @@ a few limitations with this setup:
|
|
225
225
|
distinguishing between identically named tests from different parts of
|
226
226
|
multinode job.
|
227
227
|
|
228
|
-
Callbacks
|
229
|
-
|
228
|
+
Callbacks Triggers
|
229
|
+
------------------
|
230
230
|
|
231
231
|
In SQUAD, callbacks can be attached to Builds. They are triggered once the given build finishes fetching all test jobs from the backend.
|
232
232
|
|
@@ -281,4 +281,49 @@ It's important to point out that:
|
|
281
281
|
* The callback headers will be merged with the build's project settings if available. If header names collide, project settings will get overwritten
|
282
282
|
* Callbacks are available in read-only mode at ``GET /api/builds/<id>/callbacks/``
|
283
283
|
|
284
|
+
|
285
|
+
Receiving Callbacks
|
286
|
+
-------------------
|
287
|
+
|
288
|
+
SQUAD also supports receiving callbacks. Currently the use case for receiving callbacks is for
|
289
|
+
backends that need to push results back to SQUAD while not having a live connection like LAVA
|
290
|
+
does with ZMQ or websockets. Tuxsuite is a good example as it runs on a serverless architecture
|
291
|
+
it doesn't provide live connections, thus needing the callback feature.
|
292
|
+
|
293
|
+
The callback URL should be in format:
|
294
|
+
|
295
|
+
* `POST /api/fetchjob/<group_slug>/<project_slug>/<build_version>/<environment-slug>/<backend-name>`
|
296
|
+
|
297
|
+
Authetication and payloads are dependant on the backend implementation. There is currently only one
|
298
|
+
supported backend: Tuxsuite. In the section below we will describe how this integration should work.
|
299
|
+
|
300
|
+
Use case: Tuxsuite
|
301
|
+
~~~~~~~~~~~~~~~~~~
|
302
|
+
|
303
|
+
SQUAD allows callbacks to be triggered by Tuxsuite. Developers trigger builds and tests to Tuxsuite as
|
304
|
+
they would normally do. The difference now is that they can pass a URL to be POST'ed after such build or
|
305
|
+
test is finished. Below is an example of how to do that:
|
306
|
+
|
307
|
+
.. code-block:: bash
|
308
|
+
|
309
|
+
$ tuxsuite build \
|
310
|
+
--git-repo https://github.com/torvalds/linux.git \
|
311
|
+
--git-ref master \
|
312
|
+
--target-arch arm \
|
313
|
+
--toolchain gcc-12 \
|
314
|
+
--kconfig tinyconfig \
|
315
|
+
--callback https://squad.com/api/fetchjob/tuxgroup/tuxproject/mybuild/myenv/tuxsuite
|
316
|
+
|
317
|
+
This tells Tuxsuite to POST to `https://squad.com/api/fetchjob/tuxgroup/tuxproject/mybuild/myenv/tuxsuite`.
|
318
|
+
Payload and authentication are Tuxsuite-specific and documentation can be found at https://docs.tuxsuite.com/callbacks/.
|
319
|
+
|
320
|
+
In order to validate that the request is coming from Tuxsuite, SQUAD checks the `x-tux-payload-signature` header and
|
321
|
+
match it with public key configured in each project setting page.
|
322
|
+
|
323
|
+
SQUAD will attempt to read key from `TUXSUITE_PUBLIC_KEY` variable defined in the project settings of `tuxgroup/tuxproject`.
|
324
|
+
If the request is valid, SQUAD will take in the payload provided by Tuxsuite, save it and enqueue a test job for fetching.
|
325
|
+
|
326
|
+
The main difference now is that Tuxsuite will be the one telling SQUAD when to fetch results. This prevents SQUAD from polling
|
327
|
+
Tuxsuite every now and then.
|
328
|
+
|
284
329
|
.. vim: ts=4 sw=4 et=1
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import json
|
2
|
+
|
1
3
|
from django.http import HttpResponse, HttpResponseBadRequest
|
2
4
|
from django.shortcuts import get_object_or_404
|
3
5
|
from django.views.decorators.csrf import csrf_exempt
|
@@ -8,6 +10,7 @@ from squad.ci.exceptions import SubmissionIssue
|
|
8
10
|
from squad.ci.tasks import submit, fetch
|
9
11
|
from squad.ci.models import Backend, TestJob
|
10
12
|
from squad.core.utils import log_addition
|
13
|
+
from squad.core.models import Project
|
11
14
|
|
12
15
|
|
13
16
|
@require_http_methods(["POST"])
|
@@ -149,3 +152,42 @@ def resubmit_job(request, test_job_id, method='resubmit'):
|
|
149
152
|
@csrf_exempt
|
150
153
|
def force_resubmit_job(request, test_job_id):
|
151
154
|
return resubmit_job(request, test_job_id, method='force_resubmit')
|
155
|
+
|
156
|
+
|
157
|
+
@require_http_methods(["POST"])
|
158
|
+
@csrf_exempt
|
159
|
+
def fetch_job(request, group_slug, project_slug, version, environment_slug, backend_name):
|
160
|
+
try:
|
161
|
+
backend = Backend.objects.get(name=backend_name)
|
162
|
+
except Backend.DoesNotExist:
|
163
|
+
return HttpResponseBadRequest("requested backend does not exist")
|
164
|
+
|
165
|
+
if not backend.supports_callbacks():
|
166
|
+
return HttpResponseBadRequest("requested backend does not support callbacks")
|
167
|
+
|
168
|
+
try:
|
169
|
+
project = Project.objects.get(slug=project_slug, group__slug=group_slug)
|
170
|
+
except Project.DoesNotExist:
|
171
|
+
return HttpResponseBadRequest("group/project does not exist")
|
172
|
+
|
173
|
+
try:
|
174
|
+
backend.validate_callback(request, project)
|
175
|
+
except Exception as e:
|
176
|
+
return HttpResponseBadRequest(f"request is not valid for this backend: {e}")
|
177
|
+
|
178
|
+
environment, _ = project.environments.get_or_create(slug=environment_slug)
|
179
|
+
build, _ = project.builds.get_or_create(version=version)
|
180
|
+
|
181
|
+
try:
|
182
|
+
payload = json.loads(request.body)
|
183
|
+
except Exception as e:
|
184
|
+
return HttpResponseBadRequest(f"payload failed to parse as json: {e}")
|
185
|
+
|
186
|
+
try:
|
187
|
+
test_job = backend.process_callback(payload, build, environment)
|
188
|
+
except Exception as e:
|
189
|
+
return HttpResponseBadRequest(f"malformed callback payload: {e}")
|
190
|
+
|
191
|
+
fetch.delay(test_job.id)
|
192
|
+
|
193
|
+
return HttpResponse(test_job.id, status=201)
|
@@ -21,6 +21,7 @@ urlpatterns = [
|
|
21
21
|
url(r'^submit/(%s)/(%s)/(%s)/(%s)' % (group_slug_pattern, slug_pattern, slug_pattern, slug_pattern), views.add_test_run),
|
22
22
|
url(r'^submitjob/(%s)/(%s)/(%s)/(%s)' % (group_slug_pattern, slug_pattern, slug_pattern, slug_pattern), ci.submit_job),
|
23
23
|
url(r'^watchjob/(%s)/(%s)/(%s)/(%s)' % (group_slug_pattern, slug_pattern, slug_pattern, slug_pattern), ci.watch_job),
|
24
|
+
url(r'^fetchjob/(%s)/(%s)/(%s)/(%s)/(%s)' % (group_slug_pattern, slug_pattern, slug_pattern, slug_pattern, slug_pattern), ci.fetch_job),
|
24
25
|
url(r'^data/(%s)/(%s)' % (group_slug_pattern, slug_pattern), data.get),
|
25
26
|
url(r'^resubmit/([0-9]+)', ci.resubmit_job),
|
26
27
|
url(r'^forceresubmit/([0-9]+)', ci.force_resubmit_job),
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import asyncio
|
2
|
+
import aiohttp
|
1
3
|
import json
|
2
4
|
import re
|
3
5
|
import requests
|
@@ -8,13 +10,14 @@ import yaml
|
|
8
10
|
import xmlrpc
|
9
11
|
import zmq
|
10
12
|
|
13
|
+
from asgiref.sync import sync_to_async
|
11
14
|
from dateutil.parser import isoparse
|
12
15
|
from contextlib import contextmanager
|
13
16
|
from io import BytesIO, TextIOWrapper, StringIO
|
14
17
|
from zmq.utils.strtypes import u
|
15
18
|
|
16
19
|
from xmlrpc import client as xmlrpclib
|
17
|
-
from urllib.parse import urlsplit, urljoin
|
20
|
+
from urllib.parse import urlsplit, urljoin, urlparse
|
18
21
|
|
19
22
|
|
20
23
|
from squad.ci.models import TestJob
|
@@ -160,6 +163,41 @@ class Backend(BaseBackend):
|
|
160
163
|
raise FetchIssue(self.url_remove_token(str(fault)))
|
161
164
|
|
162
165
|
def listen(self):
|
166
|
+
if not self.listen_websocket():
|
167
|
+
self.listen_zmq()
|
168
|
+
|
169
|
+
def listen_websocket(self):
|
170
|
+
async def handler():
|
171
|
+
url = urlparse(self.data.url)
|
172
|
+
ws_url = f"{url.scheme}://{self.data.username}:{self.data.token}@{url.netloc}/ws/"
|
173
|
+
try:
|
174
|
+
while True:
|
175
|
+
try:
|
176
|
+
async with aiohttp.ClientSession() as session:
|
177
|
+
self.log_debug(f"connecting to {url.scheme}://{url.netloc}/ws/")
|
178
|
+
async with session.ws_connect(ws_url, heartbeat=30) as ws:
|
179
|
+
async for msg in ws:
|
180
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
181
|
+
try:
|
182
|
+
(topic, uuid, dt, username, data) = (m for m in msg.json()[:])
|
183
|
+
data = json.loads(data)
|
184
|
+
if "error" in data:
|
185
|
+
raise aiohttp.ClientError(data["error"])
|
186
|
+
except ValueError:
|
187
|
+
continue
|
188
|
+
await sync_to_async(self.receive_event)(topic, data)
|
189
|
+
await asyncio.sleep(1)
|
190
|
+
except aiohttp.ClientError as e:
|
191
|
+
self.log_warn(f"Failed to start client: {e}")
|
192
|
+
return False
|
193
|
+
except Exception as e:
|
194
|
+
# Fall back to ZMQ
|
195
|
+
self.log_warn(f"Failed to maintain websocket connection: {e}")
|
196
|
+
return False
|
197
|
+
|
198
|
+
asyncio.run(handler())
|
199
|
+
|
200
|
+
def listen_zmq(self):
|
163
201
|
listener_url = self.get_listener_url()
|
164
202
|
if not listener_url:
|
165
203
|
self.log_warn("Can't connect, no listener URL")
|
@@ -104,6 +104,25 @@ class Backend(object):
|
|
104
104
|
"""
|
105
105
|
raise NotImplementedError
|
106
106
|
|
107
|
+
def supports_callbacks(self):
|
108
|
+
"""
|
109
|
+
Returns True if this backend supports callbacks, False otherwise
|
110
|
+
"""
|
111
|
+
return False
|
112
|
+
|
113
|
+
def validate_callback(self, request, project):
|
114
|
+
"""
|
115
|
+
Raises an exception in case the request does not pass the validation
|
116
|
+
"""
|
117
|
+
raise NotImplementedError
|
118
|
+
|
119
|
+
def process_callback(self, json_payload, build, environment, backend):
|
120
|
+
"""
|
121
|
+
Returns a test_job if processing callback's payload fine, or raise exceptions
|
122
|
+
if something isn't right
|
123
|
+
"""
|
124
|
+
raise NotImplementedError
|
125
|
+
|
107
126
|
def format_message(self, msg):
|
108
127
|
if self.data and hasattr(self.data, "name"):
|
109
128
|
return self.data.name + ': ' + msg
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import base64
|
1
2
|
import hashlib
|
2
3
|
import logging
|
3
4
|
import re
|
@@ -8,8 +9,15 @@ import json
|
|
8
9
|
from functools import reduce
|
9
10
|
from urllib.parse import urljoin
|
10
11
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
13
|
+
from cryptography.hazmat.primitives import (
|
14
|
+
hashes,
|
15
|
+
serialization,
|
16
|
+
)
|
17
|
+
|
11
18
|
from squad.ci.backend.null import Backend as BaseBackend
|
12
19
|
from squad.ci.exceptions import FetchIssue, TemporaryFetchIssue
|
20
|
+
from squad.ci.models import TestJob
|
13
21
|
|
14
22
|
|
15
23
|
logger = logging.getLogger('squad.ci.backend.tuxsuite')
|
@@ -83,16 +91,43 @@ class Backend(BaseBackend):
|
|
83
91
|
# The regex below is supposed to find only one match
|
84
92
|
return matches[0]
|
85
93
|
|
94
|
+
def generate_job_id(self, result_type, result):
|
95
|
+
"""
|
96
|
+
The job id for TuxSuite results is generated using 3 pieces of info:
|
97
|
+
1. If it's either "BUILD" or "TEST" result;
|
98
|
+
2. The TuxSuite project. Ex: "linaro/anders"
|
99
|
+
3. The ksuid of the object. Ex: "1yPYGaOEPNwr2pfqBgONY43zORp"
|
100
|
+
|
101
|
+
A couple examples for job_id are:
|
102
|
+
- BUILD:linaro@anders#1yPYGaOEPNwr2pCqBgONY43zORq
|
103
|
+
- TEST:arm@bob#1yPYGaOEPNwr2pCqBgONY43zORp
|
104
|
+
|
105
|
+
Then it's up to SQUAD's TuxSuite backend to parse the job_id
|
106
|
+
and fetch results properly.
|
107
|
+
"""
|
108
|
+
_type = "TEST" if result_type == "test" else "BUILD"
|
109
|
+
project = result["project"].replace("/", "@")
|
110
|
+
uid = result["uid"]
|
111
|
+
return f"{_type}:{project}#{uid}"
|
112
|
+
|
86
113
|
def fetch_url(self, *urlbits):
|
87
114
|
url = reduce(urljoin, urlbits)
|
88
115
|
|
89
116
|
try:
|
90
117
|
response = requests.get(url)
|
91
118
|
except Exception as e:
|
92
|
-
raise TemporaryFetchIssue(f"Can't retrieve from {url}:
|
119
|
+
raise TemporaryFetchIssue(f"Can't retrieve from {url}: {e}")
|
93
120
|
|
94
121
|
return response
|
95
122
|
|
123
|
+
def fetch_from_results_input(self, test_job):
|
124
|
+
try:
|
125
|
+
return json.loads(test_job.input)
|
126
|
+
except Exception as e:
|
127
|
+
logger.error(f"Can't parse results from job's input: {e}")
|
128
|
+
|
129
|
+
return None
|
130
|
+
|
96
131
|
def parse_build_results(self, test_job, job_url, results, settings):
|
97
132
|
required_keys = ['build_status', 'warnings_count', 'download_url', 'retry']
|
98
133
|
self.__check_required_keys__(required_keys, results)
|
@@ -163,6 +198,10 @@ class Backend(BaseBackend):
|
|
163
198
|
if results['result'] == 'fail':
|
164
199
|
test_job.failure = str(results['results'])
|
165
200
|
|
201
|
+
elif results['result'] == 'error':
|
202
|
+
test_job.failure = 'tuxsuite infrastructure error'
|
203
|
+
return 'Incomplete', completed, metadata, tests, metrics, logs
|
204
|
+
|
166
205
|
# If boot result is unkown, a retry is needed, otherwise, it either passed or failed
|
167
206
|
if 'unknown' == results['results']['boot']:
|
168
207
|
return None
|
@@ -175,6 +214,9 @@ class Backend(BaseBackend):
|
|
175
214
|
_, _, test_id = self.parse_job_id(test_job.job_id)
|
176
215
|
build_id = results['waiting_for']
|
177
216
|
build_url = job_url.replace(test_id, build_id).replace('tests', 'builds')
|
217
|
+
|
218
|
+
# TODO: check if we can save a few seconds by querying a testjob that
|
219
|
+
# already contains build results
|
178
220
|
build_metadata = self.fetch_url(build_url).json()
|
179
221
|
|
180
222
|
build_metadata_keys = settings.get('TEST_BUILD_METADATA_KEYS', [])
|
@@ -207,7 +249,12 @@ class Backend(BaseBackend):
|
|
207
249
|
|
208
250
|
def fetch(self, test_job):
|
209
251
|
url = self.job_url(test_job)
|
210
|
-
|
252
|
+
if test_job.input:
|
253
|
+
results = self.fetch_from_results_input(test_job)
|
254
|
+
test_job.input = None
|
255
|
+
else:
|
256
|
+
results = self.fetch_url(url).json()
|
257
|
+
|
211
258
|
if results.get('state') != 'finished':
|
212
259
|
return None
|
213
260
|
|
@@ -249,3 +296,49 @@ class Backend(BaseBackend):
|
|
249
296
|
url = urljoin(self.data.url, endpoint)
|
250
297
|
response = requests.post(url)
|
251
298
|
return response.status_code == 200
|
299
|
+
|
300
|
+
def supports_callbacks(self):
|
301
|
+
return True
|
302
|
+
|
303
|
+
def validate_callback(self, request, project):
|
304
|
+
signature = request.headers.get("x-tux-payload-signature", None)
|
305
|
+
if signature is None:
|
306
|
+
raise Exception("tuxsuite request is missing signature headers")
|
307
|
+
|
308
|
+
public_key = project.get_setting("TUXSUITE_PUBLIC_KEY")
|
309
|
+
if public_key is None:
|
310
|
+
raise Exception("missing tuxsuite public key for this project")
|
311
|
+
|
312
|
+
payload = request.body
|
313
|
+
signature = base64.urlsafe_b64decode(signature)
|
314
|
+
key = serialization.load_ssh_public_key(public_key.encode("ascii"))
|
315
|
+
key.verify(
|
316
|
+
signature,
|
317
|
+
payload,
|
318
|
+
ec.ECDSA(hashes.SHA256()),
|
319
|
+
)
|
320
|
+
|
321
|
+
def process_callback(self, json_payload, build, environment, backend):
|
322
|
+
if "kind" not in json_payload or "status" not in json_payload:
|
323
|
+
raise Exception("`kind` and `status` are required in the payload")
|
324
|
+
|
325
|
+
kind = json_payload["kind"]
|
326
|
+
status = json_payload["status"]
|
327
|
+
job_id = self.generate_job_id(kind, status)
|
328
|
+
try:
|
329
|
+
# Tuxsuite's job id DO NOT repeat, like ever
|
330
|
+
testjob = TestJob.objects.get(job_id=job_id, target_build=build, environment=environment.slug)
|
331
|
+
except TestJob.DoesNotExist:
|
332
|
+
testjob = TestJob.objects.create(
|
333
|
+
backend=backend,
|
334
|
+
target=build.project,
|
335
|
+
target_build=build,
|
336
|
+
environment=environment.slug,
|
337
|
+
submitted=True,
|
338
|
+
job_id=job_id
|
339
|
+
)
|
340
|
+
|
341
|
+
# Saves the input so it can be processed by the queue
|
342
|
+
testjob.input = json.dumps(status)
|
343
|
+
|
344
|
+
return testjob
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Generated by Django 4.2 on 2023-04-26 16:50
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
import django.db.models.deletion
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
('ci', '0028_create_testjob_indexes'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.CreateModel(
|
15
|
+
name='ResultsInput',
|
16
|
+
fields=[
|
17
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
18
|
+
('text', models.TextField(blank=True, null=True)),
|
19
|
+
('test_job', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='results_input', to='ci.testjob')),
|
20
|
+
],
|
21
|
+
),
|
22
|
+
]
|
@@ -164,6 +164,15 @@ class Backend(models.Model):
|
|
164
164
|
def check_job_definition(self, definition):
|
165
165
|
return self.get_implementation().check_job_definition(definition)
|
166
166
|
|
167
|
+
def supports_callbacks(self):
|
168
|
+
return self.get_implementation().supports_callbacks()
|
169
|
+
|
170
|
+
def validate_callback(self, request, project):
|
171
|
+
self.get_implementation().validate_callback(request, project)
|
172
|
+
|
173
|
+
def process_callback(self, payload, build, environment):
|
174
|
+
return self.get_implementation().process_callback(payload, build, environment, self)
|
175
|
+
|
167
176
|
def __str__(self):
|
168
177
|
return '%s (%s)' % (self.name, self.implementation_type)
|
169
178
|
|
@@ -241,6 +250,21 @@ class TestJob(models.Model):
|
|
241
250
|
return self.backend.get_implementation().job_url(self)
|
242
251
|
return None
|
243
252
|
|
253
|
+
@property
|
254
|
+
def input(self):
|
255
|
+
try:
|
256
|
+
return self.results_input.text
|
257
|
+
except ResultsInput.DoesNotExist:
|
258
|
+
return None
|
259
|
+
|
260
|
+
@input.setter
|
261
|
+
def input(self, value):
|
262
|
+
if value:
|
263
|
+
self.results_input = ResultsInput(text=value)
|
264
|
+
self.results_input.save()
|
265
|
+
else:
|
266
|
+
self.results_input.delete()
|
267
|
+
|
244
268
|
def resubmit(self):
|
245
269
|
ret_value = False
|
246
270
|
if self.can_resubmit:
|
@@ -311,3 +335,8 @@ class TestJob(models.Model):
|
|
311
335
|
indexes = [
|
312
336
|
models.Index(fields=['submitted', 'fetched']),
|
313
337
|
]
|
338
|
+
|
339
|
+
|
340
|
+
class ResultsInput(models.Model):
|
341
|
+
test_job = models.OneToOneField(TestJob, related_name='results_input', on_delete=models.CASCADE, null=True)
|
342
|
+
text = models.TextField(null=True, blank=True)
|
@@ -4,6 +4,7 @@ from django.conf import settings
|
|
4
4
|
from squad.core.models import ProjectStatus
|
5
5
|
from squad.core.plugins import Plugin as BasePlugin
|
6
6
|
from squad.frontend.templatetags.squad import project_url
|
7
|
+
from urllib.parse import urljoin
|
7
8
|
|
8
9
|
|
9
10
|
def build_url(build):
|
@@ -11,7 +12,6 @@ def build_url(build):
|
|
11
12
|
|
12
13
|
|
13
14
|
class Plugin(BasePlugin):
|
14
|
-
|
15
15
|
@staticmethod
|
16
16
|
def __github_post__(build, endpoint, payload):
|
17
17
|
api_url = build.patch_source.url
|
@@ -22,10 +22,11 @@ class Plugin(BasePlugin):
|
|
22
22
|
"Authorization": "token %s" % api_token,
|
23
23
|
}
|
24
24
|
|
25
|
-
url =
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
url = urljoin(
|
26
|
+
api_url, endpoint.format(
|
27
|
+
owner=owner,
|
28
|
+
repository=repository,
|
29
|
+
commit=commit)
|
29
30
|
)
|
30
31
|
return requests.post(url, headers=headers, json=payload)
|
31
32
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = '1.67'
|
@@ -145,6 +145,7 @@ squad/ci/migrations/0025_backend_listen_enabled.py
|
|
145
145
|
squad/ci/migrations/0026_job_start_end_time.py
|
146
146
|
squad/ci/migrations/0027_add_tuxsuite_implementation_type.py
|
147
147
|
squad/ci/migrations/0028_create_testjob_indexes.py
|
148
|
+
squad/ci/migrations/0029_create_testjob_results_input.py
|
148
149
|
squad/ci/migrations/__init__.py
|
149
150
|
squad/ci/templates/squad/ci/testjob_resubmit.html.jinja2
|
150
151
|
squad/ci/templates/squad/ci/testjob_resubmit.txt.jinja2
|
@@ -41,6 +41,7 @@ class CiApiTest(TestCase):
|
|
41
41
|
Token.objects.create(user=self.project_admin_user, key='adminkey')
|
42
42
|
|
43
43
|
self.backend = models.Backend.objects.create(name='lava', implementation_type='fake')
|
44
|
+
self.tuxsuite = models.Backend.objects.create(name='tuxsuite', implementation_type='tuxsuite')
|
44
45
|
self.client = APIClient('thekey')
|
45
46
|
self.submitter_client = APIClient('thesubmitterkey')
|
46
47
|
self.restclient = RestAPIClient('thekey')
|
@@ -302,6 +303,58 @@ class CiApiTest(TestCase):
|
|
302
303
|
r = self.client.post('/api/watchjob/mygroup/myproject/1/myenv', args)
|
303
304
|
self.assertEqual(400, r.status_code)
|
304
305
|
|
306
|
+
def test_fetch_testjob_not_supported_by_backend(self):
|
307
|
+
# Backend does not exist
|
308
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/does-not-exist')
|
309
|
+
self.assertEqual(400, r.status_code)
|
310
|
+
|
311
|
+
# Backend does not support callbacks
|
312
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/lava')
|
313
|
+
self.assertEqual(400, r.status_code)
|
314
|
+
|
315
|
+
def test_fetch_testjob_group_project_does_not_exist(self):
|
316
|
+
r = self.client.post('/api/fetchjob/doesnotexist/myproject/1/myenv/lava')
|
317
|
+
self.assertEqual(400, r.status_code)
|
318
|
+
|
319
|
+
r = self.client.post('/api/fetchjob/mygroup/doesnotexist/1/myenv/lava')
|
320
|
+
self.assertEqual(400, r.status_code)
|
321
|
+
|
322
|
+
@patch("squad.ci.tasks.fetch.delay")
|
323
|
+
@patch("squad.ci.backend.tuxsuite.Backend.process_callback")
|
324
|
+
@patch("squad.ci.backend.tuxsuite.Backend.validate_callback")
|
325
|
+
def test_fetch_testjob_invalid(self, mock_validate_callback, mock_process_callback, mock_fetch):
|
326
|
+
mock_validate_callback.side_effect = Exception("bad")
|
327
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/tuxsuite')
|
328
|
+
self.assertEqual(400, r.status_code)
|
329
|
+
mock_validate_callback.assert_called()
|
330
|
+
mock_validate_callback.reset_mock()
|
331
|
+
|
332
|
+
mock_validate_callback.side_effect = None
|
333
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/tuxsuite')
|
334
|
+
self.assertEqual(400, r.status_code)
|
335
|
+
mock_validate_callback.assert_called()
|
336
|
+
mock_validate_callback.reset_mock()
|
337
|
+
|
338
|
+
mock_process_callback.side_effect = Exception("bad")
|
339
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/tuxsuite', {"a": 1}, content_type="application/json")
|
340
|
+
self.assertEqual(400, r.status_code)
|
341
|
+
mock_validate_callback.assert_called()
|
342
|
+
mock_process_callback.assert_called()
|
343
|
+
|
344
|
+
mock_fetch.assert_not_called()
|
345
|
+
|
346
|
+
@patch("squad.ci.tasks.fetch.delay")
|
347
|
+
@patch("squad.ci.backend.tuxsuite.Backend.process_callback")
|
348
|
+
@patch("squad.ci.backend.tuxsuite.Backend.validate_callback")
|
349
|
+
def test_fetch_testjob(self, mock_validate_callback, mock_process_callback, mock_fetch):
|
350
|
+
testjob = models.TestJob(id=123)
|
351
|
+
mock_process_callback.return_value = testjob
|
352
|
+
r = self.client.post('/api/fetchjob/mygroup/myproject/1/myenv/tuxsuite', {"a": 1}, content_type="application/json")
|
353
|
+
self.assertEqual(201, r.status_code)
|
354
|
+
mock_validate_callback.assert_called()
|
355
|
+
mock_process_callback.assert_called()
|
356
|
+
mock_fetch.assert_called_with(testjob.id)
|
357
|
+
|
305
358
|
@patch('squad.ci.models.Backend.get_implementation')
|
306
359
|
def test_resubmit_submitter(self, get_implementation):
|
307
360
|
impl = MagicMock()
|